You've already forked netbox-proxmox-power-button
125 lines
5.0 KiB
Python
125 lines
5.0 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# Copyright (C) 2026 Bryan Joshua Pedini
|
|
import logging
|
|
|
|
from django.contrib import messages
|
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.views import View
|
|
|
|
from core.choices import ObjectChangeActionChoices
|
|
from virtualization.choices import VirtualMachineStatusChoices
|
|
from virtualization.models import VirtualMachine
|
|
|
|
from .proxmox import ProxmoxError, power_action
|
|
|
|
logger = logging.getLogger("proxmox_power_button")
|
|
|
|
VALID_ACTIONS = {"start", "stop", "reboot"}
|
|
|
|
|
|
def _log_changelog_only(request, vm, message):
|
|
"""
|
|
Record a changelog entry for an operation that changes no field.
|
|
|
|
Used for reboot: the VM stays powered on, so there is nothing to save. We
|
|
build the ObjectChange by hand (pre == post, no field diff) rather than
|
|
touching the object, and attribute it to the current request/user exactly
|
|
like ChangeLoggingMiddleware would.
|
|
"""
|
|
vm.snapshot()
|
|
objectchange = vm.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
|
|
objectchange.message = message
|
|
objectchange.user = request.user
|
|
objectchange.request_id = request.id
|
|
objectchange.save()
|
|
|
|
|
|
def _save_with_changelog(request, vm, status, message):
|
|
"""
|
|
Apply a status change; the post_save signal writes a single changelog
|
|
entry carrying our message.
|
|
"""
|
|
vm.snapshot()
|
|
vm._changelog_message = message
|
|
vm.status = status
|
|
vm.save()
|
|
|
|
|
|
class VMPowerActionView(PermissionRequiredMixin, View):
|
|
"""
|
|
POST-only action endpoint (Post/Redirect/Get) — renders no page.
|
|
|
|
Performs the Proxmox power action, records exactly one changelog entry per
|
|
state change, and logs every outcome (including failures).
|
|
"""
|
|
|
|
permission_required = "virtualization.change_virtualmachine"
|
|
|
|
def get(self, request, pk, action):
|
|
# Not a real page — just bounce back to the VM.
|
|
return redirect("virtualization:virtualmachine", pk=pk)
|
|
|
|
def post(self, request, pk, action):
|
|
# Scope the lookup through NetBox's object-permission system: a user
|
|
# whose change_virtualmachine permission is constraint-scoped gets a
|
|
# 404 on VMs outside their scope, exactly like core NetBox views.
|
|
vm = get_object_or_404(
|
|
VirtualMachine.objects.restrict(request.user, "change"), pk=pk
|
|
)
|
|
|
|
if action not in VALID_ACTIONS:
|
|
logger.error("user=%s vm=%s: unknown power action '%s'", request.user, vm.name, action)
|
|
messages.error(request, f"Unknown power action '{action}'.")
|
|
return redirect("virtualization:virtualmachine", pk=vm.pk)
|
|
|
|
try:
|
|
result = power_action(vm, action)
|
|
except ProxmoxError as exc:
|
|
logger.error("user=%s vm=%s: %s failed: %s", request.user, vm.name, action, exc)
|
|
# Don't echo raw Proxmox/network errors to the browser — they can
|
|
# leak internal hostnames, URLs, and response bodies. Full detail
|
|
# is in the log line above.
|
|
messages.error(
|
|
request,
|
|
f"Proxmox {action} failed for {vm.name} — see the NetBox log for details.",
|
|
)
|
|
return redirect("virtualization:virtualmachine", pk=vm.pk)
|
|
|
|
if action == "start":
|
|
if result == "running":
|
|
_save_with_changelog(
|
|
request, vm,
|
|
VirtualMachineStatusChoices.STATUS_ACTIVE,
|
|
"Powered on via Proxmox (confirmed running)",
|
|
)
|
|
logger.info("user=%s vm=%s: powered on (confirmed running)", request.user, vm.name)
|
|
messages.success(request, f"{vm.name} is powered on (confirmed running).")
|
|
else:
|
|
# Command accepted but the VM is not running yet: no state
|
|
# change, so no changelog entry — log only.
|
|
state = result or "unknown"
|
|
logger.warning(
|
|
"user=%s vm=%s: start sent but not confirmed running (state=%s)",
|
|
request.user, vm.name, state,
|
|
)
|
|
messages.warning(
|
|
request,
|
|
f"Start sent to {vm.name}, but it is not reporting 'running' yet "
|
|
f"({state}); status left unchanged.",
|
|
)
|
|
elif action == "stop":
|
|
_save_with_changelog(
|
|
request, vm,
|
|
VirtualMachineStatusChoices.STATUS_OFFLINE,
|
|
"Powered off via Proxmox (shutdown sent)",
|
|
)
|
|
logger.info("user=%s vm=%s: stop (shutdown) sent", request.user, vm.name)
|
|
messages.success(request, f"Stop sent to {vm.name}.")
|
|
else: # reboot — VM stays powered on: changelog entry, but no save
|
|
_log_changelog_only(request, vm, "Rebooted via Proxmox")
|
|
logger.info("user=%s vm=%s: reboot sent", request.user, vm.name)
|
|
messages.success(request, f"Reboot sent to {vm.name}.")
|
|
|
|
return redirect("virtualization:virtualmachine", pk=vm.pk)
|