2525
2626# Resource types
2727PUBSUB_TOPIC_RESOURCE = "pubsub_topic"
28+ PUBSUB_SUBSCRIPTION_RESOURCE = "pubsub_subscription"
2829
2930# Storage constants
3031STORAGE_PREFIX = "stale_cleaner/"
3435
3536# Time constants (in seconds)
3637DEFAULT_PUBSUB_TOPIC_THRESHOLD = 86400 # 1 day
38+ DEFAULT_PUBSUB_SUBSCRIPTION_THRESHOLD = 86400 # 1 day
3739DEFAULT_TIME_THRESHOLD = 3600 # 1 hour
3840
3941# Default values for testing
@@ -285,6 +287,12 @@ def delete_stale(self, dry_run: bool = True) -> None:
285287
286288# PubSub topic cleaner
287289class PubSubTopicCleaner (StaleCleaner ):
290+ """
291+ This cleaner will delete PubSub topics that are stale based on the time threshold.
292+ It uses the PubSub API to list and delete topics.
293+ It also applies prefix filtering to only delete topics that match the specified prefixes.
294+ """
295+
288296 def __init__ (self , project_id : str , bucket_name : str ,
289297 prefixes : list = None , time_threshold : int = DEFAULT_PUBSUB_TOPIC_THRESHOLD ,
290298 clock : Clock = None ) -> None :
@@ -304,7 +312,50 @@ def _delete_resource(self, resource_name: str) -> None:
304312 print (f"{ self .clock ()} - Deleting PubSub topic { resource_name } " )
305313 self .client .delete_topic (request = {"topic" : resource_name })
306314
307- if __name__ == "__main__" :
315+ # PubSub Subscription cleaner
316+ class PubSubSubscriptionCleaner (StaleCleaner ):
317+ """
318+ This cleaner will delete PubSub subscriptions that are stale based on the time threshold.
319+ It uses the PubSub API to list and delete subscriptions.
320+ It also applies prefix filtering to only delete subscriptions that match the specified prefixes.
321+ It checks if the subscription is detached (whether it has a topic associated with it).
322+ If it is detached, it will be considered stale and eligible for deletion.
323+ """
324+
325+ def __init__ (self , project_id : str , bucket_name : str ,
326+ prefixes : list = None , time_threshold : int = DEFAULT_PUBSUB_SUBSCRIPTION_THRESHOLD ,
327+ clock : Clock = None ) -> None :
328+ super ().__init__ (project_id , PUBSUB_SUBSCRIPTION_RESOURCE , bucket_name , prefixes , time_threshold , clock )
329+ self .client = None # Will be initialized in each method that needs it
330+
331+ def _active_resources (self ) -> dict :
332+ d = {}
333+ self .client = pubsub_v1 .SubscriberClient ()
334+
335+ with self .client :
336+ for subscription in self .client .list_subscriptions (request = {"project" : self .project_path }):
337+ subscription_name = subscription .name
338+ # Apply prefix filtering if prefixes are defined
339+ if not self .prefixes or any (subscription_name .startswith (f"{ self .project_path } /subscriptions/{ prefix } " ) for prefix in self .prefixes ):
340+ # Check if the subscription has a topic associated with it
341+ if subscription .detached :
342+ d [subscription_name ] = GoogleCloudResource (resource_name = subscription_name , clock = self .clock )
343+
344+ return d
345+
346+ def _delete_resource (self , resource_name : str ) -> None :
347+ self .client = pubsub_v1 .SubscriberClient ()
348+ print (f"{ self .clock ()} - Deleting PubSub subscription { resource_name } " )
349+ with self .client :
350+ subscription_path = self .client .subscription_path (self .project_id , resource_name )
351+ self .client .delete_subscription (request = {"subscription" : subscription_path })
352+
353+ def clean_pubsub_topics ():
354+ """ Clean up stale PubSub topics in the specified GCP project.
355+ This function initializes the PubSubTopicCleaner with the default project ID and bucket name,
356+ and a predefined list of topic prefixes.
357+ It then refreshes the resources and deletes any stale topics.
358+ """
308359 project_id = DEFAULT_PROJECT_ID
309360 bucket_name = DEFAULT_BUCKET_NAME
310361
@@ -355,3 +406,32 @@ def _delete_resource(self, resource_name: str) -> None:
355406
356407 # Delete stale resources
357408 cleaner .delete_stale (dry_run = False )
409+
410+ def clean_pubsub_subscriptions ():
411+ """ Clean up stale PubSub subscriptions in the specified GCP project.
412+ This function initializes the PubSubSubscriptionCleaner with the default project ID and bucket name,
413+ and a predefined list of subscription prefixes.
414+ It then refreshes the resources and deletes any stale subscriptions.
415+ """
416+ project_id = DEFAULT_PROJECT_ID
417+ bucket_name = DEFAULT_BUCKET_NAME
418+
419+ # No prefixes are defined for subscriptions so we will delete all stale subscriptions
420+ prefixes = []
421+
422+ # Create a PubSubSubscriptionCleaner instance
423+ cleaner = PubSubSubscriptionCleaner (project_id = project_id , bucket_name = bucket_name ,
424+ prefixes = prefixes , time_threshold = DEFAULT_PUBSUB_SUBSCRIPTION_THRESHOLD )
425+
426+ # Refresh resources
427+ cleaner .refresh ()
428+
429+ # Delete stale resources
430+ cleaner .delete_stale (dry_run = True ) # Keep dry_run=True to avoid accidental deletions during testing
431+
432+ if __name__ == "__main__" :
433+ # Clean up stale PubSub topics
434+ clean_pubsub_topics ()
435+
436+ # Clean up stale PubSub subscriptions
437+ clean_pubsub_subscriptions ()
0 commit comments