Skip to content

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

bash
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 pytest

You do not need a Proxmox host to work on most of this — the test suite mocks SSH throughout.

Running the tests

bash
pytest -q                                    # everything
pytest tests/test_vm_hotplug.py -v           # one file
pytest -k cooldown -v                        # by name

344 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.

bash
export VMA_E2E_HOST=proxmox.example.internal
export VMA_E2E_KEY=~/.ssh/id_rsa
export VMA_E2E_VMID=9001
pytest -m e2e -v

VMA_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.

bash
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 pass

No 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_case for functions and variables, PascalCase for classes
  • Docstrings on public methods, saying what the method does and what it returns
  • Type hints where the surrounding code uses them — autoscale.py and billing_tracker.py are annotated, vm_manager.py and ssh_utils.py largely 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 paramiko

Explain 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

  1. Branch from mainfix/... or feat/...
  2. Make the change, add tests, update docs
  3. pytest -q and shellcheck -S warning install.sh clean
  4. 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:

AreaWhat is needed
Guest-side verificationqm set failures are caught now, but QEMU accepting a command is not the guest honouring it
Per-node host limitshost_limits is still one pair of numbers for every node
Encrypted SSH keysA passphrase option, or ssh-agent support
Configurable step sizesFixed at 1 core and 512 MB
SmoothingOne 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.