feat(actions): scoped Proxmox power channel
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
16
lib/actions/channels/proxmox.js
Normal file
16
lib/actions/channels/proxmox.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Proxmox guest power via a SCOPED PVEAPIToken (VM.PowerMgmt on whitelisted guests
|
||||
// only). PVE enforces permissions server-side; this adapter never builds shell commands.
|
||||
export async function powerGuest({ node, vmid, op, kindPath = 'lxc' }, {
|
||||
apiUrl = process.env.PROXMOX_API_URL,
|
||||
token = process.env.PROXMOX_API_TOKEN,
|
||||
fetchImpl = fetch
|
||||
} = {}) {
|
||||
const url = `${apiUrl}/api2/json/nodes/${node}/${kindPath}/${vmid}/status/${op}`;
|
||||
const res = await fetchImpl(url, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `PVEAPIToken=${token}` }
|
||||
});
|
||||
if (!res.ok) throw new Error(`proxmox ${op} ${vmid} → ${res.status} ${await res.text?.() ?? ''}`);
|
||||
const body = await res.json();
|
||||
return { ok: true, upid: body?.data ?? null };
|
||||
}
|
||||
20
tests/actions/proxmox.test.js
Normal file
20
tests/actions/proxmox.test.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { powerGuest } from '../../lib/actions/channels/proxmox.js';
|
||||
|
||||
describe('proxmox channel', () => {
|
||||
it('POSTs the scoped power op with the token header', async () => {
|
||||
const fetchMock = vi.fn(async () => ({ ok: true, status: 200, json: async () => ({ data: 'UPID:...' }) }));
|
||||
const out = await powerGuest({ node: 'z', vmid: 107, op: 'stop', kindPath: 'lxc' },
|
||||
{ apiUrl: 'https://pve:8006', token: 'user@pve!void=secret', fetchImpl: fetchMock });
|
||||
expect(out.ok).toBe(true);
|
||||
const [url, opts] = fetchMock.mock.calls[0];
|
||||
expect(url).toBe('https://pve:8006/api2/json/nodes/z/lxc/107/status/stop');
|
||||
expect(opts.method).toBe('POST');
|
||||
expect(opts.headers.Authorization).toBe('PVEAPIToken=user@pve!void=secret');
|
||||
});
|
||||
it('throws on a non-ok response', async () => {
|
||||
const fetchMock = vi.fn(async () => ({ ok: false, status: 403, text: async () => 'forbidden' }));
|
||||
await expect(powerGuest({ node: 'z', vmid: 1, op: 'stop', kindPath: 'lxc' },
|
||||
{ apiUrl: 'https://pve:8006', token: 't', fetchImpl: fetchMock })).rejects.toThrow(/403/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user