diff --git a/.gitignore b/.gitignore index 021805b..6b78c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ target/ # Project-specific venv +proxmox_scripts/settings.py diff --git a/proxmox_scripts/auth.py b/proxmox_scripts/auth.py new file mode 100644 index 0000000..718f89d --- /dev/null +++ b/proxmox_scripts/auth.py @@ -0,0 +1,19 @@ +from proxmoxer import ProxmoxAPI +from . import settings + +import typing as t + + +_api: t.Optional[ProxmoxAPI] = None + + +def get_api() -> ProxmoxAPI: + global _api + if _api is None: + _api = ProxmoxAPI( + settings.PROXMOX_HOST, + user=settings.PROXMOX_USER, + password=settings.PROXMOX_PASS(), + verify_ssl=True, + ) + return _api diff --git a/proxmox_scripts/settings.example.py b/proxmox_scripts/settings.example.py new file mode 100644 index 0000000..e96e521 --- /dev/null +++ b/proxmox_scripts/settings.example.py @@ -0,0 +1,5 @@ +import typing as t + +PROXMOX_HOST: str = "hv1.example.com:8006" # FIXME +PROXMOX_USER: str = "username@pam" # FIXME +PROXMOX_PASS: t.Callable[[], str] = lambda: "changeme" # FIXME diff --git a/proxmox_scripts/snapshots.py b/proxmox_scripts/snapshots.py new file mode 100644 index 0000000..cd13c47 --- /dev/null +++ b/proxmox_scripts/snapshots.py @@ -0,0 +1,53 @@ +from . import auth +from . import util +import datetime +import typing as t +from proxmoxer.core import ProxmoxResource +from argparse import ArgumentParser + + +def list_snapshots() -> list: + snapshots = [] + + for vmid, vm in util.qemu_vms().items(): + for snapshot in vm['api'].snapshot().get(): + if snapshot['name'] != 'current': + snapshot['node'] = vm['node'] + snapshot['vmid'] = vmid + snapshot['vmname'] = vm['name'] + snapshot['api'] = vm['api'].snapshot(snapshot['name']) + snapshots.append(snapshot) + return snapshots + +def review_snapshots(): + parser = ArgumentParser() + parser.add_argument('--older-than', help='More than N days ago only', type=int, + default=0) + parser.add_argument('--delete', '-d', help='Offer to delete snapshots', + action='store_true') + args = parser.parse_args() + + filter_date = datetime.datetime.now() - datetime.timedelta(days=args.older_than) + + for snap in list_snapshots(): + snap_date = datetime.datetime.fromtimestamp(snap['snaptime']) + snap_age = datetime.datetime.now() - snap_date + if snap_date > filter_date: + continue + + print( + f"{snap['node']} -- [{snap['vmid']}] {snap['vmname']} -- " + f"at {snap_date} ({snap_age.days}d ago)") + for attr, val in snap.items(): + if attr in ('node', 'vmid', 'vmname', 'snap_date', 'snaptime', 'api'): + continue + print(f" {attr}: {val}") + + if args.delete: + print(" Delete? [vmid to delete] ", end='') + res = input().strip() + if res == str(snap['vmid']): + snap['api'].delete() + print(" Deleted.") + + print("") diff --git a/proxmox_scripts/util.py b/proxmox_scripts/util.py new file mode 100644 index 0000000..484492e --- /dev/null +++ b/proxmox_scripts/util.py @@ -0,0 +1,20 @@ +from . import auth + + +def nodes() -> dict[str, dict]: + """ List of hypervisors """ + nodes = auth.get_api().nodes.get() + return {node['node']: node for node in nodes} + + +def qemu_vms() -> dict: + """ Flat dict of QEMU VMs on all hypervisors """ + out = {} + api = auth.get_api() + + for node in nodes(): + for vm in api.nodes(node).qemu.get(): + vm['node'] = node + vm['api'] = api.nodes(node).qemu(vm['vmid']) + out[vm['vmid']] = vm + return out diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bddd3bc --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +from setuptools import setup, find_packages + + +def parse_requirements(): + reqs = [] + with open("requirements.txt", "r") as handle: + for line in handle: + reqs.append(line) + return reqs + + +setup( + name="proxmox_scripts", + version="0.1.0", + description="Collection of misc scripts to manage proxmox", + author="tobast", + author_email="contact@tobast.fr", + license="LICENSE", + url="https://git.tobast.fr/tobast/proxmox_scripts/", + packages=find_packages(), + include_package_data=True, + long_description=open("README.md").read(), + install_requires=parse_requirements(), + entry_points={ + "console_scripts": [ + ( + "proxmox-snapshot-review = proxmox_scripts.snapshots:review_snapshots", + ), + ] + }, +) +