From a1ec88e2909083769630bc589cb9469930bc3b13 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Mon, 5 Jan 2026 12:12:37 +0000 Subject: [PATCH] openstack-cleanup: delete old keypairs as well (#12833) * openstack-cleanup: format and logging * openstack-cleanup: delete old keypairs as well --- scripts/openstack-cleanup/main.py | 85 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/scripts/openstack-cleanup/main.py b/scripts/openstack-cleanup/main.py index d10e4725b..80011399d 100755 --- a/scripts/openstack-cleanup/main.py +++ b/scripts/openstack-cleanup/main.py @@ -5,42 +5,38 @@ import logging import datetime import time -DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' -PAUSE_SECONDS = 5 +log = logging.getLogger(__name__) -log = logging.getLogger('openstack-cleanup') +parser = argparse.ArgumentParser(description="Cleanup OpenStack resources") -parser = argparse.ArgumentParser(description='Cleanup OpenStack resources') - -parser.add_argument('-v', '--verbose', action='store_true', - help='Increase verbosity') -parser.add_argument('--hours', type=int, default=4, - help='Age (in hours) of VMs to cleanup (default: 4h)') -parser.add_argument('--dry-run', action='store_true', - help='Do not delete anything') +parser.add_argument( + "--hours", + type=int, + default=4, + help="Age (in hours) of VMs to cleanup (default: 4h)", +) +parser.add_argument("--dry-run", action="store_true", help="Do not delete anything") args = parser.parse_args() -oldest_allowed = datetime.datetime.now() - datetime.timedelta(hours=args.hours) +oldest_allowed = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( + hours=args.hours +) def main(): + logging.basicConfig(level=logging.INFO) if args.dry_run: - print('Running in dry-run mode') - else: - print('This will delete resources... (ctrl+c to cancel)') - time.sleep(PAUSE_SECONDS) + log.info("Running in dry-run mode") conn = openstack.connect() - print('Servers...') - map_if_old(conn.compute.delete_server, - conn.compute.servers()) + log.info("Deleting servers...") + map_if_old(conn.compute.delete_server, conn.compute.servers()) - print('Ports...') + log.info("Deleting ports...") try: - map_if_old(conn.network.delete_port, - conn.network.ports()) + map_if_old(conn.network.delete_port, conn.network.ports()) except openstack.exceptions.ConflictException as ex: # Need to find subnet-id which should be removed from a router for sn in conn.network.subnets(): @@ -48,40 +44,41 @@ def main(): fn_if_old(conn.network.delete_subnet, sn) except openstack.exceptions.ConflictException: for r in conn.network.routers(): - print("Deleting subnet %s from router %s", sn, r) + log.info("Deleting subnet %s from router %s", sn, r) try: - conn.network.remove_interface_from_router( - r, subnet_id=sn.id) + conn.network.remove_interface_from_router(r, subnet_id=sn.id) except Exception as ex: - print("Failed to delete subnet from router as %s", ex) + log.error("Failed to delete subnet from router", exc_info=True) for ip in conn.network.ips(): fn_if_old(conn.network.delete_ip, ip) # After removing unnecessary subnet from router, retry to delete ports - map_if_old(conn.network.delete_port, - conn.network.ports()) + map_if_old(conn.network.delete_port, conn.network.ports()) - print('Security groups...') + log.info("Deleting security groups...") try: - map_if_old(conn.network.delete_security_group, - conn.network.security_groups()) + map_if_old(conn.network.delete_security_group, conn.network.security_groups()) except openstack.exceptions.ConflictException as ex: # Need to delete port when security groups is in used - map_if_old(conn.network.delete_port, - conn.network.ports()) - map_if_old(conn.network.delete_security_group, - conn.network.security_groups()) + map_if_old(conn.network.delete_port, conn.network.ports()) + map_if_old(conn.network.delete_security_group, conn.network.security_groups()) - print('Subnets...') - map_if_old(conn.network.delete_subnet, - conn.network.subnets()) + log.info("Deleting Subnets...") + map_if_old(conn.network.delete_subnet, conn.network.subnets()) - print('Networks...') + log.info("Deleting networks...") for n in conn.network.networks(): if not n.is_router_external: fn_if_old(conn.network.delete_network, n) + log.info("Deleting keypairs...") + map_if_old( + conn.compute.delete_keypair, + (conn.compute.get_keypair(x.name) for x in conn.compute.keypairs()), + # LIST API for keypairs does not give us a created_at (WTF) + ) + # runs the given fn to all elements of the that are older than allowed def map_if_old(fn, items): @@ -91,15 +88,19 @@ def map_if_old(fn, items): # run the given fn function only if the passed item is older than allowed def fn_if_old(fn, item): - created_at = datetime.datetime.strptime(item.created_at, DATE_FORMAT) + created_at = datetime.datetime.fromisoformat(item.created_at) + if created_at.tzinfo is None: + created_at = created_at.replace(tzinfo=datetime.timezone.utc) + # Handle TZ unaware object by assuming UTC + # Can't compare to oldest_allowed otherwise if item.name == "default": # skip default security group return if created_at < oldest_allowed: - print('Will delete %(name)s (%(id)s)' % item) + log.info("Will delete %s %s)", item.name, item.id) if not args.dry_run: fn(item) -if __name__ == '__main__': +if __name__ == "__main__": # execute only if run as a script main()