Skip to main content

openstack-janitor

CI

A CLI that audits an OpenStack cloud for orphaned and wasteful resources.

Status: early development. Seven detectors and a clean command are working — see Detectors and Cleaning; more detectors and safety rails are coming — see Roadmap.

Install

Requires Python 3.9+. On older interpreters, pip automatically selects a compatible older version of openstacksdk.

From PyPI:

pipx install openstack-janitor   # recommended for CLI use
# or
pip install openstack-janitor

Standalone Linux binary — no Python needed at all. Built against glibc 2.28, so it runs on RHEL 8-era hosts whose system Python is too old for the package:

curl -LO https://github.com/mabunemeh/openstack-janitor/releases/latest/download/janitor-linux-x86_64
chmod +x janitor-linux-x86_64
./janitor-linux-x86_64 audit --cloud my-cloud

From source:

git clone https://github.com/mabunemeh/openstack-janitor
cd openstack-janitor
pip install -e .

Old distro pip (e.g. Ubuntu 22.04's pip 22.0): source installs can fail with No module named 'packaging.licenses' — the distro-patched pip leaks the system's old packaging into the build environment. Installing from PyPI is unaffected. For source installs, use a fresh venv with an upgraded pip: python3 -m venv .venv && .venv/bin/pip install -U pip.

Usage

janitor detectors
janitor audit
janitor audit -c my-cloud
janitor audit -d unattached-volumes -d orphaned-ports
janitor audit -f json > findings.json
janitor audit -f html > report.html
janitor audit --long

Short options: -c / --cloud, -d / --detector, -C / --config, -f / --format, -h / --help.

--config / -C points at a janitor.toml; without it, auto-discovery checks (in order) $JANITOR_CONFIG, ./janitor.toml, then ~/.config/janitor/janitor.toml ($XDG_CONFIG_HOME honoured). See Configuration.

janitor detectors lists every registered detector (name and description) without connecting to a cloud. Use the names it prints with audit --detector.

--format table (the default) prints a rich table; json and html write machine-readable / shareable reports to stdout.

--long / -l prints all extra columns.

Example output when orphaned volumes are found:

$ janitor audit --cloud my-cloud
              openstack-janitor findings
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Detector            ┃ Type   ┃ ID        ┃ Name    ┃ Project ┃ Reason                       ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ unattached-volumes  │ volume │ a1b2c3d4… │ old-db  │ proj-1  │ volume is unattached         │
│                     │        │           │         │         │ (status=available)           │
└─────────────────────┴────────┴───────────┴─────────┴─────────┴──────────────────────────────┘
$ echo $?
1

The Detector column appears when more than one detector runs (the default, or two or more -d flags). With a single -d, the column is omitted. Use the name to run janitor clean -d <detector>.

janitor audit exits 0 when nothing is found, 1 when findings were reported (so it's safe to wire into a cron job or CI check), 2 if an unknown --detector name is given or --config names a missing/invalid file, and 3 if connecting to the cloud fails.

Configuration

# janitor.toml
[detectors]
disabled = ["shutoff-instances"]          # detector names to skip (default: none)

[detectors.old-snapshots]
max_age_days = 30                          # default 90

[detectors.shutoff-instances]
max_age_days = 7                           # default 30

[clean]
exclude = ["vol-0001", "sg-0002"]          # standing keep-list, merged with -e

--detector overrides a config-disabled detector; --exclude is merged with clean.exclude rather than replacing it.

Cleaning

janitor clean -d unattached-volumes --dry-run         # preview only, deletes nothing
janitor clean -d unattached-volumes                   # preview, then prompt before deleting
janitor clean -d unattached-volumes --yes             # preview and delete without prompting
janitor clean -d unattached-volumes -e vol-0001 --yes # keep specific resource IDs

janitor clean detects once, prints the plan for that set, then either exits (--dry-run), asks for confirmation (default), or deletes without asking (--yes). Deletes are real and irreversible: once a volume, snapshot, floating IP, port, security group, instance, or image is gone, it is gone.

--detector is required. clean refuses to act on every detector at once, so a single command can never delete across all seven resource types.

Read this before deleting:

  • Some detectors have no age threshold. orphaned-ports and unused-security-groups flag by state alone, so a port or group created seconds ago — mid-provisioning, mid-CI-run — is a finding and will be deleted. Until tag/age rails land, keep --detector narrow.
  • Within one invocation the printed plan is what gets deleted. Detection runs once; confirmation or --yes acts on that same set. A later clean run re-detects from scratch.
  • Cleaning can create new findings. Deleting a shutoff instance leaves its volumes unattached; deleting snapshots orphans the images built from them. The next run will flag those. Re-read each plan rather than looping --yes blindly.
  • --exclude takes resource IDs, not names. An --exclude value that matches no finding aborts the run rather than being ignored, so a typo cannot silently delete what it was meant to protect.
  • Deletes are asynchronous. A successful call means the delete was accepted; verify with janitor audit afterwards.
  • Non-interactive terminals need --yes or --dry-run. Bare clean refuses to prompt when stdin is not a TTY (for example in CI).

Tag/age safety rails (e.g. a janitor:keep marker) are on the roadmap but not implemented yet.

janitor clean exits 0 on a successful dry run, declined confirmation, or execute, 1 if any resource was not deleted during execute — either the deletion failed or the detector does not support cleaning (other resources are still processed; failures are isolated per resource) — 2 for a missing or unknown --detector, a missing/invalid --config file, an --exclude ID that matched nothing, --dry-run combined with --yes, or a prompt required on a non-interactive terminal, and 3 if connecting to the cloud or scanning it fails.

Detectors

Name Flags
unattached-volumes Volumes in available status with no attachments.
unassociated-floating-ips Floating IPs not associated with any port.
orphaned-ports Ports with no device owner and no device id. Infrastructure ports (DHCP, routers, load balancer VIPs) always carry one of these, so they are never flagged; a pre-created port awaiting attachment will be.
old-snapshots Volume snapshots older than a threshold (default 90 days).
shutoff-instances Instances in SHUTOFF status whose last update is older than a threshold (default 30 days). There is no "shutoff since" field in the Compute API, so the age is a conservative lower bound — the detector may under-report but never over-reports.
unused-security-groups Security groups not attached to any port and not referenced as a remote_group_id by any rule. The per-project default group is always skipped.
orphan-snapshot-images Glance images whose block_device_mapping references a Cinder volume snapshot that no longer exists. Includes hidden images.

Detection is always read-only — janitor audit never modifies anything. The same detectors also know how to delete what they flag, but only janitor clean --yes ever does so.

Resources without a parseable timestamp are never flagged by the age-based detectors. Note that orphaned-ports and unused-security-groups have no age threshold at all, so they can flag a resource created seconds ago — see the warning under Cleaning. old-snapshots and shutoff-instances thresholds are configurable — see Configuration.

Authentication

openstack-janitor uses openstacksdk for authentication, so anything openstacksdk understands works here too:

  • A named cloud from clouds.yaml via --cloud my-cloud (or the OS_CLOUD environment variable).
  • The standard OS_* environment variables (OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_PROJECT_NAME, etc.) if no cloud is specified.

See the openstacksdk configuration documentation for the full resolution order and file locations.

Roadmap

  • Safety rails: a janitor:keep tag (or similar) so resources can be marked "do not touch" before clean ever deletes anything, beyond today's --exclude flag and dry-run review.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openstack_janitor-0.5.0.tar.gz (46.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openstack_janitor-0.5.0-py3-none-any.whl (35.0 kB view details)

Uploaded Python 3

File details

Details for the file openstack_janitor-0.5.0.tar.gz.

File metadata

  • Download URL: openstack_janitor-0.5.0.tar.gz
  • Upload date:
  • Size: 46.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openstack_janitor-0.5.0.tar.gz
Algorithm Hash digest
SHA256 1e59d84e55436f345d986eab2903018e8f0d85911c12d0e2cb6fbab42d667dc5
MD5 0d7bd5bd3d708d7d01a438840e36cb8d
BLAKE2b-256 8f75281a6e248bcc15f23fe13b32cec1858485917d2e52461701553174de9fa9

See more details on using hashes here.

Provenance

The following attestation bundles were made for openstack_janitor-0.5.0.tar.gz:

Publisher: release.yml on mabunemeh/openstack-janitor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openstack_janitor-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for openstack_janitor-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f962e3302943d2b085520173770b8654bd2fc1148c02f71f9dc1100fddd80ae
MD5 3c64ee91b9ea67dd552e180bcfef4a59
BLAKE2b-256 79db219a48fe76d57186502c7adc3e9091a3885a9044ca0970caccb32d8f7277

See more details on using hashes here.

Provenance

The following attestation bundles were made for openstack_janitor-0.5.0-py3-none-any.whl:

Publisher: release.yml on mabunemeh/openstack-janitor

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.7.0

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page