Contributing
Bug reports, fixes and features are all welcome. This page covers the practical parts; CONTRIBUTING.md in the repository is the canonical version.
Before you start
For anything beyond a bug fix, open an issue first. It is much less frustrating than finding out after the work is done that the direction was wrong.
Worth reading first: architecture for how the pieces fit, and known limitations for what is already known — several of those are good first contributions.
Development setup
git clone https://github.com/YOUR_USERNAME/proxmox-vm-autoscale.git
cd proxmox-vm-autoscale
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt pytestYou do not need a Proxmox host to work on most of this — the test suite mocks SSH throughout.
Running the tests
pytest -q # everything
pytest tests/test_vm_hotplug.py -v # one file
pytest -k cooldown -v # by name344 tests, in about four seconds. CI runs them on Python 3.10 through 3.12, plus shellcheck -S warning install.sh.
The end-to-end suite
A separate suite drives a real Proxmox node as root and resizes a real VM. It is marked e2e, skipped by default, and will never be in CI — CI has no hypervisor to talk to.
export VMA_E2E_HOST=proxmox.example.internal
export VMA_E2E_KEY=~/.ssh/id_rsa
export VMA_E2E_VMID=9001
pytest -m e2e -vVMA_E2E_USER (default root), VMA_E2E_PORT, VMA_E2E_PASSWORD and VMA_E2E_KNOWN_HOSTS are also read. Nothing is configured in the repository, so no node address reaches a commit. Without VMA_E2E_HOST the suite skips; a partial configuration fails loudly instead, because silently skipping on a typo is the exact class of defect this project has spent several releases removing.
Two guards stand between a mistyped VMID and someone's production guest:
- the suite refuses any VM whose name does not contain
testbed; - it restores a fixed baseline before and after every test, asserts the VM is actually at that baseline, and leaves the guest powered off if it found it that way.
Point it at a disposable VM and nothing else.
The entry criterion for a test here is that a mock could not have told us. Everything a fake can answer belongs in the unit suite; duplicating that coverage buys nothing and costs a real VM. The suite exists because it earns its keep: a single session against real hardware found three defects while 303 mocked tests passed, and its own first run found a fourth — every RAM scale-up was being rejected by the hypervisor on the ordinary guest configuration, because every fixture encoded the same wrong assumption the code did.
When it finds something, the fix lands with a unit test built from the shape the node actually returned — tests/test_real_proxmox_shapes.py holds those, copied verbatim — so the regression is caught in CI rather than only on hardware.
What a good change looks like
Tests that fail before the fix. The most useful thing you can include is a test that demonstrates the bug against main. It proves the problem is real and stops it coming back.
git stash # set your fix aside
pytest tests/test_your_new_test.py # should fail
git stash pop
pytest tests/test_your_new_test.py # should passNo unrelated changes. Reformatting a file you happened to open makes the actual change impossible to see.
Documentation updated in the same PR when behaviour changes. The docs live in docs/; every page has an "Edit this page" link.
A changelog entry under ## [Unreleased] in CHANGELOG.md for anything user-visible.
Code style
Match what is already there. Specifically:
- 4-space indentation, no tabs
snake_casefor functions and variables,PascalCasefor classes- Docstrings on public methods, saying what the method does and what it returns
- Type hints where the surrounding code uses them —
autoscale.pyandbilling_tracker.pyare annotated,vm_manager.pyandssh_utils.pylargely are not; follow the file - Comments explain why, not what. The code already says what it does
There is no linter in CI and no formatter config, so consistency is by hand.
Commit messages
Conventional-commit prefixes, imperative mood:
fix: enforce scaling_limits from config.yaml
feat: add per-VM threshold overrides
docs: document the balloon fallback path
test: cover NUMA detection with no numa line
chore: bump paramikoExplain the reasoning in the body when a one-line subject cannot carry it. A reader six months from now needs to know why, not just what.
Pull requests
- Branch from
main—fix/...orfeat/... - Make the change, add tests, update docs
pytest -qandshellcheck -S warning install.shclean- Push and open the PR
In the description: what changed, why, how you verified it, and any behaviour change existing installations will notice.
Keeping the documentation honest
tests/test_docs_match_code.py fails the build when the documentation and the code disagree: a configuration key the schema accepts but the reference does not mention, a metric the service exports but the operations guide does not list, a module absent from the module reference, or a version claimed in the docs that is not the one in version.py.
It exists because the opposite happened repeatedly. Thirteen files once had to be corrected in a single change, and stale claims — test counts, whether ssh_port was required, whether only RSA keys loaded — were each found after the code had already moved. Nothing checked any of it.
The tests check presence and agreement, not prose. Whether a sentence is any good is a judgement no test makes.
Areas that need work
Pulled from known limitations, roughly by value:
| Area | What is needed |
|---|---|
| Guest-side verification | qm set failures are caught now, but QEMU accepting a command is not the guest honouring it |
| Per-node host limits | host_limits is still one pair of numbers for every node |
| Encrypted SSH keys | A passphrase option, or ssh-agent support |
| Configurable step sizes | Fixed at 1 core and 512 MB |
| Smoothing | One instantaneous sample per cycle; a moving average would cut flapping |
Reporting bugs
Open an issue with:
- What you expected and what happened
- Steps to reproduce
- Proxmox version (
pveversion), Python version, OS - Relevant log excerpts
- Your config with every credential removed
Security issues go through responsible disclosure instead — never a public issue.
Code of conduct
Participation is covered by the Code of Conduct.
Licence
Contributions are licensed under the MIT Licence, same as the project.