From 2b37e8431dcbd04dd5bdc3af6a20177c71ece858 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Thu, 15 Jan 2026 18:32:20 +0530 Subject: [PATCH 1/9] feat(secretmanager): Adding tags samples --- secretmanager/snippets/detach_tag_binding.py | 85 ++++++++++++++ secretmanager/snippets/list_tag_bindings.py | 81 ++++++++++++++ .../regional_samples/detach_regional_tag.py | 105 ++++++++++++++++++ .../list_regional_secret_tag_bindings.py | 91 +++++++++++++++ .../regional_samples/snippets_test.py | 49 +++++++- secretmanager/snippets/snippets_test.py | 52 ++++++++- 6 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 secretmanager/snippets/detach_tag_binding.py create mode 100644 secretmanager/snippets/list_tag_bindings.py create mode 100644 secretmanager/snippets/regional_samples/detach_regional_tag.py create mode 100644 secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py diff --git a/secretmanager/snippets/detach_tag_binding.py b/secretmanager/snippets/detach_tag_binding.py new file mode 100644 index 00000000000..4afa9153566 --- /dev/null +++ b/secretmanager/snippets/detach_tag_binding.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +# [START secretmanager_detach_tag_binding] +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}" + parent = f"//secretmanager.googleapis.com/{secret_name}" + + # Find the binding name for the given tag value + binding_name = None + request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) + + for binding in client.list_tag_bindings(request=request): + if binding.tag_value == tag_value: + binding_name = binding.name + break + + if binding_name is None: + print(f"Tag binding for value {tag_value} not found on {secret_name}.") + return + + # Delete the tag binding + request = resourcemanager_v3.DeleteTagBindingRequest(name=binding_name) + operation = client.delete_tag_binding(request=request) + + # Wait for the operation to complete + operation.result() + + print(f"Detached tag value {tag_value} from {secret_name}") + + +# [END secretmanager_detach_tag_binding] + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="id of the GCP project") + parser.add_argument( + "secret_id", help="id of the secret to detach tag from" + ) + parser.add_argument( + "tag_value", + help="tag value to detach (e.g., 'tagValues/123456789012')", + ) + args = parser.parse_args() + + detach_tag(args.project_id, args.secret_id, args.tag_value) diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py new file mode 100644 index 00000000000..ea04fed9c16 --- /dev/null +++ b/secretmanager/snippets/list_tag_bindings.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +""" +command line application and sample code for listing tag bindings attached to a secret. +""" + +import argparse + +# [START secretmanager_list_tag_bindings] +# Import the Resource Manager client library. +from google.cloud import resourcemanager_v3 +from google.cloud import secretmanager + + +def list_tag_bindings(project_id: str, secret_id: str) -> None: + """ + Lists all tag bindings attached to a secret. + + Args: + project_id (str): The project ID where the secret exists. + secret_id (str): The ID of the secret to list tag bindings for. + + Example: + # List tag bindings for a secret + list_tag_bindings("my-project", "my-secret") + """ + + # Create the Resource Manager client. + client = resourcemanager_v3.TagBindingsClient() + sm_client = secretmanager.SecretManagerServiceClient() + + # Build the resource name of the parent secret. + secret_name = sm_client.secret_path(project_id, secret_id) + + parent = f"//secretmanager.googleapis.com/{secret_name}" + + # List all tag bindings. + tag_bindings = [] + request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) + + # Retrieve and process tag bindings + bindings = client.list_tag_bindings(request=request) + count = 0 + + print(f"Tag bindings for {secret_name}:") + for binding in bindings: + print(f"- Tag Value: {binding.tag_value}") + tag_bindings.append(binding) + count += 1 + + if count == 0: + print(f"No tag bindings found for {secret_name}.") + + +# [END secretmanager_list_tag_bindings] + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="id of the GCP project") + parser.add_argument( + "secret_id", help="id of the secret to list tag bindings for" + ) + args = parser.parse_args() + + list_tag_bindings(args.project_id, args.secret_id) diff --git a/secretmanager/snippets/regional_samples/detach_regional_tag.py b/secretmanager/snippets/regional_samples/detach_regional_tag.py new file mode 100644 index 00000000000..e6b47c47ca9 --- /dev/null +++ b/secretmanager/snippets/regional_samples/detach_regional_tag.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START secretmanager_detach_regional_tag_binding] +from google.cloud import resourcemanager_v3 +from google.api_core import client_options + + +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}" + + # Find the binding with the specified tag value + binding_name = None + request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) + tag_bindings = tag_bindings_client.list_tag_bindings(request=request) + + for binding in tag_bindings: + if binding.tag_value == tag_value: + binding_name = binding.name + break + + if binding_name is None: + print(f"Tag binding for value {tag_value} not found on {secret_name}.") + return + + # Delete the tag binding + request = resourcemanager_v3.DeleteTagBindingRequest(name=binding_name) + operation = tag_bindings_client.delete_tag_binding(request=request) + + # Wait for the operation to complete + operation.result() + + print(f"Detached tag value {tag_value} from {secret_name}") + + +# [END secretmanager_detach_regional_tag_binding] + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="id of the GCP project") + parser.add_argument( + "location_id", help="id of location where secret is stored" + ) + parser.add_argument("secret_id", help="id of the secret") + parser.add_argument( + "tag_value", help="tag value to detach (e.g., tagValues/123456789012)" + ) + args = parser.parse_args() + + detach_regional_tag( + args.project_id, args.location_id, args.secret_id, args.tag_value + ) diff --git a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py new file mode 100644 index 00000000000..351d39c94f0 --- /dev/null +++ b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START secretmanager_list_regional_secret_tag_bindings] +from google.cloud import resourcemanager_v3 +from google.api_core import client_options + + +def list_regional_secret_tag_bindings( + project_id: str, location_id: str, secret_id: str +) -> None: + """ + Lists tag bindings for a regional secret. + + Args: + project_id (str): ID of the Google Cloud project + location_id (str): Region where the secret should be stored (e.g., "us-central1") + secret_id (str): ID of the secret to create + + Example: + # Create a regional secret with a customer-managed encryption key + list_regional_secret_tag_bindings( + "my-project", + "my-regional-secret-with-cmek", + "us-central1", + ) + """ + # 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 + ) + + 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/{name}" + + # List the tag bindings + print(f"Tag bindings for {name}:") + 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}.") + + +# [END secretmanager_list_regional_secret_tag_bindings] + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("project_id", help="id of the GCP project") + parser.add_argument( + "location_id", help="id of location where secret is stored" + ) + parser.add_argument("secret_id", help="id of the secret in which to list") + args = parser.parse_args() + + list_regional_secret_tag_bindings( + args.project_id, args.location_id, args.secret_id + ) diff --git a/secretmanager/snippets/regional_samples/snippets_test.py b/secretmanager/snippets/regional_samples/snippets_test.py index 436b8d0d11b..671891310fa 100644 --- a/secretmanager/snippets/regional_samples/snippets_test.py +++ b/secretmanager/snippets/regional_samples/snippets_test.py @@ -58,7 +58,8 @@ from regional_samples import update_regional_secret_with_etag from regional_samples import view_regional_secret_annotations from regional_samples import view_regional_secret_labels - +from regional_samples import list_regional_secret_tag_bindings +from regional_samples import detach_regional_tag @pytest.fixture() def location_id() -> str: @@ -858,3 +859,49 @@ def test_view_regional_secret_labels( out, _ = capsys.readouterr() assert label_key in out + +def test_list_regional_secret_tag_bindings(capsys: pytest.LogCaptureFixture, project_id: str, location_id: str, tag_key_and_tag_value: Tuple[str, str], secret_id: str) -> None: + tag_key, tag_value = tag_key_and_tag_value + create_regional_secret_with_tags.create_regional_secret_with_tags( + project_id, location_id, secret_id, tag_key, tag_value + ) + + # Call the function being tested + list_regional_secret_tag_bindings.list_regional_secret_tag_bindings(project_id, location_id, secret_id) + + # Verify the tag value is in the returned bindings + out, _ = capsys.readouterr() + assert secret_id in out + assert tag_value in out + +def test_detach_regional_tag( + capsys: pytest.LogCaptureFixture, + project_id: str, + location_id: str, + tag_key_and_tag_value: Tuple[str, str], + secret_id: str +) -> None: + tag_key, tag_value = tag_key_and_tag_value + + # Create a secret and bind the tag to it for testing detach + create_regional_secret_with_tags.create_regional_secret_with_tags( + project_id, location_id, secret_id, tag_key, tag_value + ) + + # Call the function being tested - detach the tag + detach_regional_tag.detach_regional_tag( + project_id, location_id, secret_id, tag_value + ) + + # Verify the output contains the expected message + out, _ = capsys.readouterr() + assert "Detached tag value" in out + + # List the tags to verify the tag was detached + list_regional_secret_tag_bindings.list_regional_secret_tag_bindings( + project_id, location_id, secret_id + ) + + # Verify the tag value is no longer in the returned bindings + out, _ = capsys.readouterr() + assert tag_value not in out diff --git a/secretmanager/snippets/snippets_test.py b/secretmanager/snippets/snippets_test.py index dbcdde921a2..472b8bb5af8 100644 --- a/secretmanager/snippets/snippets_test.py +++ b/secretmanager/snippets/snippets_test.py @@ -63,7 +63,8 @@ from update_secret_with_etag import update_secret_with_etag from view_secret_annotations import view_secret_annotations from view_secret_labels import view_secret_labels - +from list_tag_bindings import list_tag_bindings +from detach_tag_binding import detach_tag @pytest.fixture() def client() -> secretmanager.SecretManagerServiceClient: @@ -745,3 +746,52 @@ def test_update_secret_with_delayed_destroy(secret_with_delayed_destroy: Tuple[s updated_version_destroy_ttl_value = 118400 updated_secret = update_secret_with_delayed_destroy(project_id, secret_id, updated_version_destroy_ttl_value) assert updated_secret.version_destroy_ttl == timedelta(seconds=updated_version_destroy_ttl_value) + +def test_list_tag_bindings( + capsys: pytest.LogCaptureFixture, + project_id: str, + tag_key_and_tag_value: Tuple[str, str], + secret_id: str, +) -> None: + # Get the tag value from the fixture + _, tag_value = tag_key_and_tag_value + + # Create the secret and bind tag (using existing fixtures) + bind_tags_to_secret(project_id, secret_id, tag_value) + + # Call the function being tested + list_tag_bindings(project_id, secret_id) + + # Verify the tag value is in the returned bindings + out, _ = capsys.readouterr() + assert secret_id in out + assert tag_value in out + +def test_detach_tag( + project_id: str, + tag_key_and_tag_value: Tuple[str, str], + secret_id: str, +) -> None: + """Test detaching a tag from a secret.""" + # Get the tag value from the fixture + _, tag_value = tag_key_and_tag_value + + # First bind the tag to the secret + bind_tags_to_secret(project_id, secret_id, tag_value) + secret_name = f"projects/{project_id}/secrets/{secret_id}" + + # Now detach the tag + detach_tag(project_id, secret_id, tag_value) + + client = resourcemanager_v3.TagBindingsClient() + parent = f"//secretmanager.googleapis.com/{secret_name}" + request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) + + # Check that none of the bindings contain our tag value + tag_found = False + for binding in client.list_tag_bindings(request=request): + if binding.tag_value == tag_value: + tag_found = True + break + + assert not tag_found, f"Tag value {tag_value} should have been detached but was found" From 7531482b3b93a3ea51362ac22a3b120f0d1ff2d8 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Thu, 15 Jan 2026 20:58:41 +0530 Subject: [PATCH 2/9] feat(secretmanager): Update formatting --- .../regional_samples/detach_regional_tag.py | 2 +- .../list_regional_secret_tag_bindings.py | 2 +- .../regional_samples/snippets_test.py | 48 +++++++++++++------ secretmanager/snippets/snippets_test.py | 34 ++++++++----- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/secretmanager/snippets/regional_samples/detach_regional_tag.py b/secretmanager/snippets/regional_samples/detach_regional_tag.py index e6b47c47ca9..e2b0b344a2b 100644 --- a/secretmanager/snippets/regional_samples/detach_regional_tag.py +++ b/secretmanager/snippets/regional_samples/detach_regional_tag.py @@ -15,8 +15,8 @@ # limitations under the License. # [START secretmanager_detach_regional_tag_binding] -from google.cloud import resourcemanager_v3 from google.api_core import client_options +from google.cloud import resourcemanager_v3 def detach_regional_tag( diff --git a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py index 351d39c94f0..90b330e3f94 100644 --- a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py +++ b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py @@ -15,8 +15,8 @@ # limitations under the License. # [START secretmanager_list_regional_secret_tag_bindings] -from google.cloud import resourcemanager_v3 from google.api_core import client_options +from google.cloud import resourcemanager_v3 def list_regional_secret_tag_bindings( diff --git a/secretmanager/snippets/regional_samples/snippets_test.py b/secretmanager/snippets/regional_samples/snippets_test.py index 671891310fa..45a1a2f0f6b 100644 --- a/secretmanager/snippets/regional_samples/snippets_test.py +++ b/secretmanager/snippets/regional_samples/snippets_test.py @@ -37,6 +37,7 @@ from regional_samples import delete_regional_secret_with_etag from regional_samples import destroy_regional_secret_version from regional_samples import destroy_regional_secret_version_with_etag +from regional_samples import detach_regional_tag from regional_samples import disable_regional_secret_delayed_destroy from regional_samples import disable_regional_secret_version from regional_samples import disable_regional_secret_version_with_etag @@ -48,6 +49,7 @@ from regional_samples import get_regional_secret_version from regional_samples import iam_grant_access_with_regional_secret from regional_samples import iam_revoke_access_with_regional_secret +from regional_samples import list_regional_secret_tag_bindings from regional_samples import list_regional_secret_versions from regional_samples import list_regional_secret_versions_with_filter from regional_samples import list_regional_secrets @@ -58,8 +60,7 @@ from regional_samples import update_regional_secret_with_etag from regional_samples import view_regional_secret_annotations from regional_samples import view_regional_secret_labels -from regional_samples import list_regional_secret_tag_bindings -from regional_samples import detach_regional_tag + @pytest.fixture() def location_id() -> str: @@ -860,48 +861,65 @@ def test_view_regional_secret_labels( out, _ = capsys.readouterr() assert label_key in out -def test_list_regional_secret_tag_bindings(capsys: pytest.LogCaptureFixture, project_id: str, location_id: str, tag_key_and_tag_value: Tuple[str, str], secret_id: str) -> None: + +def test_list_regional_secret_tag_bindings( + capsys: pytest.LogCaptureFixture, + project_id: str, + location_id: str, + tag_key_and_tag_value: Tuple[str, str], + secret_id: str, +) -> None: tag_key, tag_value = tag_key_and_tag_value create_regional_secret_with_tags.create_regional_secret_with_tags( project_id, location_id, secret_id, tag_key, tag_value ) # Call the function being tested - list_regional_secret_tag_bindings.list_regional_secret_tag_bindings(project_id, location_id, secret_id) - + + list_regional_secret_tag_bindings.list_regional_secret_tag_bindings( + project_id, location_id, secret_id + ) + # Verify the tag value is in the returned bindings + out, _ = capsys.readouterr() assert secret_id in out assert tag_value in out + def test_detach_regional_tag( - capsys: pytest.LogCaptureFixture, - project_id: str, - location_id: str, - tag_key_and_tag_value: Tuple[str, str], - secret_id: str + capsys: pytest.LogCaptureFixture, + project_id: str, + location_id: str, + tag_key_and_tag_value: Tuple[str, str], + secret_id: str, ) -> None: tag_key, tag_value = tag_key_and_tag_value - + # Create a secret and bind the tag to it for testing detach + create_regional_secret_with_tags.create_regional_secret_with_tags( project_id, location_id, secret_id, tag_key, tag_value ) - + # Call the function being tested - detach the tag + detach_regional_tag.detach_regional_tag( project_id, location_id, secret_id, tag_value ) - + # Verify the output contains the expected message + out, _ = capsys.readouterr() assert "Detached tag value" in out - + # List the tags to verify the tag was detached + list_regional_secret_tag_bindings.list_regional_secret_tag_bindings( project_id, location_id, secret_id ) - + # Verify the tag value is no longer in the returned bindings + out, _ = capsys.readouterr() assert tag_value not in out diff --git a/secretmanager/snippets/snippets_test.py b/secretmanager/snippets/snippets_test.py index 472b8bb5af8..3e0a35baa0c 100644 --- a/secretmanager/snippets/snippets_test.py +++ b/secretmanager/snippets/snippets_test.py @@ -42,6 +42,7 @@ from delete_secret_with_etag import delete_secret_with_etag from destroy_secret_version import destroy_secret_version from destroy_secret_version_with_etag import destroy_secret_version_with_etag +from detach_tag_binding import detach_tag from disable_secret_version import disable_secret_version from disable_secret_version_with_etag import disable_secret_version_with_etag from disable_secret_with_delayed_destroy import disable_secret_with_delayed_destroy @@ -56,6 +57,7 @@ from list_secret_versions_with_filter import list_secret_versions_with_filter from list_secrets import list_secrets from list_secrets_with_filter import list_secrets_with_filter +from list_tag_bindings import list_tag_bindings from quickstart import quickstart from update_secret import update_secret from update_secret_with_alias import update_secret_with_alias @@ -63,8 +65,7 @@ from update_secret_with_etag import update_secret_with_etag from view_secret_annotations import view_secret_annotations from view_secret_labels import view_secret_labels -from list_tag_bindings import list_tag_bindings -from detach_tag_binding import detach_tag + @pytest.fixture() def client() -> secretmanager.SecretManagerServiceClient: @@ -747,6 +748,7 @@ def test_update_secret_with_delayed_destroy(secret_with_delayed_destroy: Tuple[s updated_secret = update_secret_with_delayed_destroy(project_id, secret_id, updated_version_destroy_ttl_value) assert updated_secret.version_destroy_ttl == timedelta(seconds=updated_version_destroy_ttl_value) + def test_list_tag_bindings( capsys: pytest.LogCaptureFixture, project_id: str, @@ -754,19 +756,24 @@ def test_list_tag_bindings( secret_id: str, ) -> None: # Get the tag value from the fixture + _, tag_value = tag_key_and_tag_value - + # Create the secret and bind tag (using existing fixtures) + bind_tags_to_secret(project_id, secret_id, tag_value) - + # Call the function being tested + list_tag_bindings(project_id, secret_id) - + # Verify the tag value is in the returned bindings + out, _ = capsys.readouterr() assert secret_id in out assert tag_value in out + def test_detach_tag( project_id: str, tag_key_and_tag_value: Tuple[str, str], @@ -774,24 +781,29 @@ def test_detach_tag( ) -> None: """Test detaching a tag from a secret.""" # Get the tag value from the fixture + _, tag_value = tag_key_and_tag_value - + # First bind the tag to the secret + bind_tags_to_secret(project_id, secret_id, tag_value) secret_name = f"projects/{project_id}/secrets/{secret_id}" - + # Now detach the tag + detach_tag(project_id, secret_id, tag_value) - + client = resourcemanager_v3.TagBindingsClient() parent = f"//secretmanager.googleapis.com/{secret_name}" request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) - + # Check that none of the bindings contain our tag value + tag_found = False for binding in client.list_tag_bindings(request=request): if binding.tag_value == tag_value: tag_found = True break - - assert not tag_found, f"Tag value {tag_value} should have been detached but was found" + assert ( + not tag_found + ), f"Tag value {tag_value} should have been detached but was found" From fb569b7e436289bd6980d01b3f994f75145b47f5 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Thu, 15 Jan 2026 22:08:14 +0530 Subject: [PATCH 3/9] feat(secretmanager): Update formatting --- secretmanager/snippets/list_tag_bindings.py | 2 -- .../list_regional_secret_tag_bindings.py | 7 +++---- secretmanager/snippets/snippets_test.py | 21 +++++++------------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py index ea04fed9c16..bcd842b20a3 100644 --- a/secretmanager/snippets/list_tag_bindings.py +++ b/secretmanager/snippets/list_tag_bindings.py @@ -47,7 +47,6 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: parent = f"//secretmanager.googleapis.com/{secret_name}" # List all tag bindings. - tag_bindings = [] request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) # Retrieve and process tag bindings @@ -57,7 +56,6 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: print(f"Tag bindings for {secret_name}:") for binding in bindings: print(f"- Tag Value: {binding.tag_value}") - tag_bindings.append(binding) count += 1 if count == 0: diff --git a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py index 90b330e3f94..569206c4f9f 100644 --- a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py +++ b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py @@ -27,15 +27,14 @@ def list_regional_secret_tag_bindings( Args: project_id (str): ID of the Google Cloud project - location_id (str): Region where the secret should be stored (e.g., "us-central1") - secret_id (str): ID of the secret to create + location_id (str): Region where the secret is stored (e.g., "us-central1") + secret_id (str): The ID of the secret to list tag bindings for. Example: - # Create a regional secret with a customer-managed encryption key list_regional_secret_tag_bindings( "my-project", - "my-regional-secret-with-cmek", "us-central1", + "my-regional-secret-with-cmek", ) """ # Set up the endpoint for the regional resource manager diff --git a/secretmanager/snippets/snippets_test.py b/secretmanager/snippets/snippets_test.py index 3e0a35baa0c..03f39276715 100644 --- a/secretmanager/snippets/snippets_test.py +++ b/secretmanager/snippets/snippets_test.py @@ -775,6 +775,7 @@ def test_list_tag_bindings( def test_detach_tag( + capsys: pytest.LogCaptureFixture, project_id: str, tag_key_and_tag_value: Tuple[str, str], secret_id: str, @@ -787,23 +788,17 @@ def test_detach_tag( # First bind the tag to the secret bind_tags_to_secret(project_id, secret_id, tag_value) - secret_name = f"projects/{project_id}/secrets/{secret_id}" # Now detach the tag detach_tag(project_id, secret_id, tag_value) - client = resourcemanager_v3.TagBindingsClient() - parent = f"//secretmanager.googleapis.com/{secret_name}" - request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) + out, _ = capsys.readouterr() + assert "Detached tag value" in out + + list_tag_bindings(project_id, secret_id) - # Check that none of the bindings contain our tag value + # Verify the tag value is no longer in the returned bindings - tag_found = False - for binding in client.list_tag_bindings(request=request): - if binding.tag_value == tag_value: - tag_found = True - break - assert ( - not tag_found - ), f"Tag value {tag_value} should have been detached but was found" + out, _ = capsys.readouterr() + assert tag_value not in out From d415d0794b182631c254484ce8935bba48f73951 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Thu, 15 Jan 2026 23:07:36 +0530 Subject: [PATCH 4/9] feat(secretmanager): Update formatting --- secretmanager/snippets/detach_tag_binding.py | 4 +--- secretmanager/snippets/list_tag_bindings.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/secretmanager/snippets/detach_tag_binding.py b/secretmanager/snippets/detach_tag_binding.py index 4afa9153566..2a3cd07629c 100644 --- a/secretmanager/snippets/detach_tag_binding.py +++ b/secretmanager/snippets/detach_tag_binding.py @@ -73,9 +73,7 @@ def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("project_id", help="id of the GCP project") - parser.add_argument( - "secret_id", help="id of the secret to detach tag from" - ) + parser.add_argument("secret_id", help="id of the secret to detach tag from") parser.add_argument( "tag_value", help="tag value to detach (e.g., 'tagValues/123456789012')", diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py index bcd842b20a3..7d586e8dc1b 100644 --- a/secretmanager/snippets/list_tag_bindings.py +++ b/secretmanager/snippets/list_tag_bindings.py @@ -71,9 +71,7 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("project_id", help="id of the GCP project") - parser.add_argument( - "secret_id", help="id of the secret to list tag bindings for" - ) + parser.add_argument("secret_id", help="id of the secret to list tag bindings for") args = parser.parse_args() list_tag_bindings(args.project_id, args.secret_id) From a79c77c85ed7304ee5d378897dd609443417cd9d Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Fri, 16 Jan 2026 00:19:03 +0530 Subject: [PATCH 5/9] feat(secretmanager): Update formatting --- secretmanager/snippets/detach_tag_binding.py | 2 +- secretmanager/snippets/list_tag_bindings.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/secretmanager/snippets/detach_tag_binding.py b/secretmanager/snippets/detach_tag_binding.py index 2a3cd07629c..438545361f2 100644 --- a/secretmanager/snippets/detach_tag_binding.py +++ b/secretmanager/snippets/detach_tag_binding.py @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import argparse # [START secretmanager_detach_tag_binding] from google.cloud import resourcemanager_v3 @@ -67,6 +66,7 @@ def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: if __name__ == "__main__": + import argparse parser = argparse.ArgumentParser( description=__doc__, diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py index 7d586e8dc1b..887b87e8d84 100644 --- a/secretmanager/snippets/list_tag_bindings.py +++ b/secretmanager/snippets/list_tag_bindings.py @@ -16,7 +16,6 @@ command line application and sample code for listing tag bindings attached to a secret. """ -import argparse # [START secretmanager_list_tag_bindings] # Import the Resource Manager client library. @@ -66,6 +65,8 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter, From fd227b1ea7eeec6e508bda954b4e9c1fdf3ca07d Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Fri, 16 Jan 2026 10:55:36 +0530 Subject: [PATCH 6/9] feat(secretmanager): Use flags instead of count --- secretmanager/snippets/list_tag_bindings.py | 6 +++--- .../regional_samples/list_regional_secret_tag_bindings.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py index 887b87e8d84..9bed66e63b1 100644 --- a/secretmanager/snippets/list_tag_bindings.py +++ b/secretmanager/snippets/list_tag_bindings.py @@ -50,14 +50,14 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: # Retrieve and process tag bindings bindings = client.list_tag_bindings(request=request) - count = 0 + found_bindings = False print(f"Tag bindings for {secret_name}:") for binding in bindings: print(f"- Tag Value: {binding.tag_value}") - count += 1 + found_bindings = True - if count == 0: + if not found_bindings: print(f"No tag bindings found for {secret_name}.") diff --git a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py index 569206c4f9f..ee50eec70cd 100644 --- a/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py +++ b/secretmanager/snippets/regional_samples/list_regional_secret_tag_bindings.py @@ -53,7 +53,7 @@ def list_regional_secret_tag_bindings( # List the tag bindings print(f"Tag bindings for {name}:") - count = 0 + found_bindings = False # Use the list_tag_bindings method to get all tag bindings request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) @@ -62,9 +62,9 @@ def list_regional_secret_tag_bindings( # Iterate through the results for binding in tag_bindings: print(f"- Tag Value: {binding.tag_value}") - count += 1 + found_bindings = True - if count == 0: + if not found_bindings: print(f"No tag bindings found for {name}.") From 6c19a869fb1af9e328ae643a344e33f5216c777e Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Fri, 16 Jan 2026 12:11:44 +0530 Subject: [PATCH 7/9] feat(secretmanager): Use v1 lib --- secretmanager/snippets/list_tag_bindings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/secretmanager/snippets/list_tag_bindings.py b/secretmanager/snippets/list_tag_bindings.py index 9bed66e63b1..240d626a830 100644 --- a/secretmanager/snippets/list_tag_bindings.py +++ b/secretmanager/snippets/list_tag_bindings.py @@ -20,7 +20,7 @@ # [START secretmanager_list_tag_bindings] # Import the Resource Manager client library. from google.cloud import resourcemanager_v3 -from google.cloud import secretmanager +from google.cloud import secretmanager_v1 def list_tag_bindings(project_id: str, secret_id: str) -> None: @@ -38,7 +38,7 @@ def list_tag_bindings(project_id: str, secret_id: str) -> None: # Create the Resource Manager client. client = resourcemanager_v3.TagBindingsClient() - sm_client = secretmanager.SecretManagerServiceClient() + sm_client = secretmanager_v1.SecretManagerServiceClient() # Build the resource name of the parent secret. secret_name = sm_client.secret_path(project_id, secret_id) From e3db20a4173690d9560a49bc868eb1d08bd80064 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Fri, 16 Jan 2026 14:30:39 +0530 Subject: [PATCH 8/9] feat(secretmanager): Use path --- secretmanager/snippets/detach_tag_binding.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/secretmanager/snippets/detach_tag_binding.py b/secretmanager/snippets/detach_tag_binding.py index 438545361f2..56b3adcd7d6 100644 --- a/secretmanager/snippets/detach_tag_binding.py +++ b/secretmanager/snippets/detach_tag_binding.py @@ -17,7 +17,7 @@ # [START secretmanager_detach_tag_binding] from google.cloud import resourcemanager_v3 - +from google.cloud import secretmanager_v1 def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: """ @@ -33,17 +33,18 @@ def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: detach_tag("my-project", "my-secret", "tagValues/123456789012") """ # Create the Resource Manager client. - client = resourcemanager_v3.TagBindingsClient() + rm_client = resourcemanager_v3.TagBindingsClient() # Build the resource name of the parent secret. - secret_name = f"projects/{project_id}/secrets/{secret_id}" + client = secretmanager_v1.SecretManagerServiceClient() + secret_name = client.secret_path(project_id, secret_id) parent = f"//secretmanager.googleapis.com/{secret_name}" # Find the binding name for the given tag value binding_name = None request = resourcemanager_v3.ListTagBindingsRequest(parent=parent) - for binding in client.list_tag_bindings(request=request): + for binding in rm_client.list_tag_bindings(request=request): if binding.tag_value == tag_value: binding_name = binding.name break @@ -54,7 +55,7 @@ def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: # Delete the tag binding request = resourcemanager_v3.DeleteTagBindingRequest(name=binding_name) - operation = client.delete_tag_binding(request=request) + operation = rm_client.delete_tag_binding(request=request) # Wait for the operation to complete operation.result() From 308099d4bf76b59e808765294e46cfb33b3904d5 Mon Sep 17 00:00:00 2001 From: Khilan Maradiya Date: Fri, 16 Jan 2026 17:24:56 +0530 Subject: [PATCH 9/9] feat(secretmanager): Update formatting --- secretmanager/snippets/detach_tag_binding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/secretmanager/snippets/detach_tag_binding.py b/secretmanager/snippets/detach_tag_binding.py index 56b3adcd7d6..78eee7b5524 100644 --- a/secretmanager/snippets/detach_tag_binding.py +++ b/secretmanager/snippets/detach_tag_binding.py @@ -19,6 +19,7 @@ from google.cloud import resourcemanager_v3 from google.cloud import secretmanager_v1 + def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None: """ Detaches a tag value from a secret.