from google.cloud import compute_v1 from actor.recycler.cloud_resource_recycler.base import CloudResourceCleaner, CleanResult from analyst.libs import consts from analyst.resource_leak.model import CloudAsset class GCPCleaner(CloudResourceCleaner): def __init__(self, dryrun: bool = False, require_delete_metadata: bool = False): """Initialize the base GCP cleaner Args: dryrun: If True, only list resources that would be cleaned without actually deleting them """ super().__init__(dryrun=dryrun, require_delete_metadata=require_delete_metadata) class GCPVMRecycler(GCPCleaner): """Class to handle recycling of leaked GCP VM instances""" def __init__(self, dryrun: bool = False): """Initialize the GCP VM recycler Args: project_id: The GCP project ID where the VMs will be cleaned dryrun: If True, only list VMs that would be cleaned without actually deleting them """ super().__init__(dryrun) self.compute_client = compute_v1.InstancesClient() self.zone_client = compute_v1.ZonesClient() def _clean_cloud_resource(self, r: CloudAsset) -> CleanResult: """Clean up a GCP VM instance Args: r: The CloudAsset representing the VM to clean up Returns: CleanResult indicating success or failure """ try: # Parse zone from resource name zone = r.region # Create delete request request = compute_v1.DeleteInstanceRequest( instance=r.rid, project=r.account_id, zone=zone ) # Execute delete operation operation = self.compute_client.delete(request=request) # Wait for operation to complete operation.result() # This will wait by default return CleanResult( asset=r, result=consts.CleanResult.Cleaned, reason=f"Successfully deleted VM instance {r.rid}" ) except Exception as e: return CleanResult( asset=r, result=consts.CleanResult.Failed, reason=f"Failed to delete VM instance {r.rid}: {str(e)}" ) class GCPDiskRecycler(GCPCleaner): def __init__(self, dryrun: bool = False): """Initialize the GCP disk recycler Args: dryrun: If True, only list disks that would be cleaned without actually deleting them """ super().__init__(dryrun) self.disk_client = compute_v1.DisksClient() def _clean_cloud_resource(self, r: CloudAsset) -> CleanResult: """Clean up a GCP disk Args: r: The CloudAsset representing the disk to clean up Returns: CleanResult indicating success or failure """ try: # Parse zone from resource name zone = r.extra_info.get('zone', 'unknown') # Create delete request request = compute_v1.DeleteDiskRequest( disk=r.rid, project=r.account_id, zone=zone, ) # Execute delete operation operation = self.disk_client.delete(request=request) # Wait for operation to complete operation.result() # This will wait by default return CleanResult( asset=r, result=consts.CleanResult.Cleaned, reason=f"Successfully deleted disk {r.rid}" ) except Exception as e: return CleanResult( asset=r, result=consts.CleanResult.Failed, reason=f"Failed to delete disk {r.rid}: {str(e)}" ) # GCPCloudNATRecycler uses GCP router APIs to delete NAT, refer to https://cloud.google.com/compute/docs/reference/rest/v1/routers/delete#request-body class GCPCloudNATRecycler(GCPCleaner): def __init__(self, dryrun: bool = False): """Initialize the GCP Cloud NAT recycler Args: dryrun: If True, only list Cloud NATs that would be cleaned without actually deleting them """ super().__init__(dryrun) self.compute_client = compute_v1.RoutersClient() def _clean_cloud_resource(self, r: CloudAsset) -> CleanResult: """Clean up a GCP Cloud NAT Args: r: The CloudAsset representing the Cloud NAT to clean up Returns: CleanResult indicating success or failure """ try: # Parse region from resource name # Create delete request request = compute_v1.DeleteRouterRequest( router=r.rname, project=r.account_id, region=r.region, ) # Execute delete operation operation = self.compute_client.delete(request=request) # Wait for operation to complete operation.result() # This will wait by default return CleanResult( asset=r, result=consts.CleanResult.Cleaned, reason=f"Successfully deleted Cloud NAT {r.rid}" ) except Exception as e: return CleanResult( asset=r, result=consts.CleanResult.Failed, reason=f"Failed to delete Cloud NAT {r.rid}: {str(e)}" ) # GCPForwardingRuleRecycler uses GCP forwarding rules APIs to delete forwarding rules class GCPForwardingRuleRecycler(GCPCleaner): def __init__(self, dryrun: bool = False): """Initialize the GCP Forwarding Rule recycler Args: dryrun: If True, only list forwarding rules that would be cleaned without actually deleting them """ super().__init__(dryrun) self.compute_client = compute_v1.ForwardingRulesClient() self.global_compute_client = compute_v1.GlobalForwardingRulesClient() def _clean_cloud_resource(self, r: CloudAsset) -> CleanResult: """Clean up a GCP Forwarding Rule Args: r: The CloudAsset representing the forwarding rule to clean up Returns: CleanResult indicating success or failure """ try: # Create delete request if r.rname.startswith('gapipsc'): request = compute_v1.DeleteGlobalForwardingRuleRequest( forwarding_rule=r.rname, project=r.account_id, ) operation = self.global_compute_client.delete(request=request) else: request = compute_v1.DeleteForwardingRuleRequest( forwarding_rule=r.rname, project=r.account_id, region=r.region, ) operation = self.compute_client.delete(request=request) # Wait for operation to complete operation.result() # This will wait by default return CleanResult( asset=r, result=consts.CleanResult.Cleaned, reason=f"Successfully deleted forwarding rule {r.rid}" ) except Exception as e: return CleanResult( asset=r, result=consts.CleanResult.Failed, reason=f"Failed to delete forwarding rule {r.rid}: {str(e)}" )