Spreadsheet IP management works right up until it doesn’t. Two people edit it, someone fat-fingers an octet, and now two devices think they own the same address. I wanted something better for the lab without buying anything, so I built a small IPAM tool with Flask and Ansible.
The split that makes it work
Python is the brain: the Flask app owns the web interface, the database of assignments, and all the decision making. Ansible is the hands: when the tool needs to actually touch a device or verify an address is really in use, it calls a playbook. Neither side does the other’s job. Python never SSHes into a switch, and Ansible never decides anything.
The boundary between them is one function:
import subprocess, json
def run_playbook(playbook, extra_vars):
result = subprocess.run(
["ansible-playbook", playbook, "-e", json.dumps(extra_vars)],
capture_output=True, text=True, timeout=120,
)
return result.returncode == 0, result.stdout
And the Flask side stays plain CRUD plus one verification hook:
@app.route("/assign", methods=["POST"])
def assign():
ip = request.form["ip"]
if db_lookup(ip):
return "Already assigned", 409
ok, output = run_playbook("verify_free.yml", {"target_ip": ip})
if not ok:
return f"Address answers on the network: {output}", 409
db_insert(ip, request.form["hostname"], request.form["role"])
return redirect("/")
The verify playbook is deliberately dumb: ping the address, check the switch ARP table, report back. Dumb is good in the hands. All the judgment about what the answer means lives in one place, on the Python side.
Why the split pays off
Testing is the big one. I can exercise all the logic without a single device connected, because the device layer is behind playbooks I can point at a lab target or run in check mode. When I change how devices get verified, the Python side doesn’t care. Same reason you don’t weld the socket to the ratchet.
It also kept credentials out of the web app entirely. The Flask side has no SSH keys and no device passwords; those live in Ansible Vault where the rest of my automation already keeps them. A compromised web app can ask Ansible to run a ping, and that’s the whole blast radius.
It’s not a product. It’s a few hundred lines that solved my problem, taught me a pattern, and gave me one more reason to keep credentials in vault instead of code. Most useful tools I’ve built look exactly like that.
