openstack-cleanup: delete old keypairs as well (#12833)

* openstack-cleanup: format and logging

* openstack-cleanup: delete old keypairs as well
This commit is contained in:
Max Gautier 2026-01-05 12:12:37 +00:00 committed by GitHub
parent c9ff62944e
commit a1ec88e290
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -5,42 +5,38 @@ import logging
import datetime import datetime
import time import time
DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ' log = logging.getLogger(__name__)
PAUSE_SECONDS = 5
log = logging.getLogger('openstack-cleanup') parser = argparse.ArgumentParser(description="Cleanup OpenStack resources")
parser = argparse.ArgumentParser(description='Cleanup OpenStack resources') parser.add_argument(
"--hours",
parser.add_argument('-v', '--verbose', action='store_true', type=int,
help='Increase verbosity') default=4,
parser.add_argument('--hours', type=int, default=4, help="Age (in hours) of VMs to cleanup (default: 4h)",
help='Age (in hours) of VMs to cleanup (default: 4h)') )
parser.add_argument('--dry-run', action='store_true', parser.add_argument("--dry-run", action="store_true", help="Do not delete anything")
help='Do not delete anything')
args = parser.parse_args() 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(): def main():
logging.basicConfig(level=logging.INFO)
if args.dry_run: if args.dry_run:
print('Running in dry-run mode') log.info("Running in dry-run mode")
else:
print('This will delete resources... (ctrl+c to cancel)')
time.sleep(PAUSE_SECONDS)
conn = openstack.connect() conn = openstack.connect()
print('Servers...') log.info("Deleting servers...")
map_if_old(conn.compute.delete_server, map_if_old(conn.compute.delete_server, conn.compute.servers())
conn.compute.servers())
print('Ports...') log.info("Deleting ports...")
try: try:
map_if_old(conn.network.delete_port, map_if_old(conn.network.delete_port, conn.network.ports())
conn.network.ports())
except openstack.exceptions.ConflictException as ex: except openstack.exceptions.ConflictException as ex:
# Need to find subnet-id which should be removed from a router # Need to find subnet-id which should be removed from a router
for sn in conn.network.subnets(): for sn in conn.network.subnets():
@ -48,40 +44,41 @@ def main():
fn_if_old(conn.network.delete_subnet, sn) fn_if_old(conn.network.delete_subnet, sn)
except openstack.exceptions.ConflictException: except openstack.exceptions.ConflictException:
for r in conn.network.routers(): 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: try:
conn.network.remove_interface_from_router( conn.network.remove_interface_from_router(r, subnet_id=sn.id)
r, subnet_id=sn.id)
except Exception as ex: 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(): for ip in conn.network.ips():
fn_if_old(conn.network.delete_ip, ip) fn_if_old(conn.network.delete_ip, ip)
# After removing unnecessary subnet from router, retry to delete ports # After removing unnecessary subnet from router, retry to delete ports
map_if_old(conn.network.delete_port, map_if_old(conn.network.delete_port, conn.network.ports())
conn.network.ports())
print('Security groups...') log.info("Deleting security groups...")
try: try:
map_if_old(conn.network.delete_security_group, map_if_old(conn.network.delete_security_group, conn.network.security_groups())
conn.network.security_groups())
except openstack.exceptions.ConflictException as ex: except openstack.exceptions.ConflictException as ex:
# Need to delete port when security groups is in used # Need to delete port when security groups is in used
map_if_old(conn.network.delete_port, map_if_old(conn.network.delete_port, conn.network.ports())
conn.network.ports()) map_if_old(conn.network.delete_security_group, conn.network.security_groups())
map_if_old(conn.network.delete_security_group,
conn.network.security_groups())
print('Subnets...') log.info("Deleting Subnets...")
map_if_old(conn.network.delete_subnet, map_if_old(conn.network.delete_subnet, conn.network.subnets())
conn.network.subnets())
print('Networks...') log.info("Deleting networks...")
for n in conn.network.networks(): for n in conn.network.networks():
if not n.is_router_external: if not n.is_router_external:
fn_if_old(conn.network.delete_network, n) 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 # runs the given fn to all elements of the that are older than allowed
def map_if_old(fn, items): 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 # run the given fn function only if the passed item is older than allowed
def fn_if_old(fn, item): 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 if item.name == "default": # skip default security group
return return
if created_at < oldest_allowed: 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: if not args.dry_run:
fn(item) fn(item)
if __name__ == '__main__': if __name__ == "__main__":
# execute only if run as a script # execute only if run as a script
main() main()