Documentation v0.1.7

SSE Events

This page describes the Server-Sent Events format go-pve-qemu uses for long-running operations and how to consume it from Go.

Endpoints using SSE: install, start, reboot, set/node.

Event Format

data: {"step":"<stage>","status":"<status>","message":"<message>"}

event: close
data: {}
Field Description
step Step name; the install flow uses <stage> > <step>, see Provisioning Pipeline
status See the table below
message Human-readable message; finished steps include the duration, e.g. (3.21s)
status Meaning
processing Step in progress, or a line of qm / init script output (indented by two spaces)
success Step finished
info Final notice (start / reboot)
error Step failed; the flow stops
message prefix Meaning
[*] Info
[+] Success
[-] Failure

Notes

Go Client Example

package main

import (
    "bufio"
    "bytes"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"
)

type event struct {
    Step    string `json:"step"`
    Status  string `json:"status"`
    Message string `json:"message"`
}

func main() {
    body, err := json.Marshal(map[string]any{
        "os":      "ubuntu",
        "version": "24.04",
        "cpu":     2,
        "ram":     4096,
        "disk":    "32G",
    })
    if err != nil {
        log.Fatal(err)
    }

    resp, err := http.Post("http://192.168.0.11:8080/api/vm/install", "application/json", bytes.NewReader(body))
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        log.Fatalf("install rejected: %s", resp.Status)
    }

    failed := false
    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        line := scanner.Text()
        if line == "event: close" {
            // server-side flow finished
            break
        }
        payload, ok := strings.CutPrefix(line, "data: ")
        if !ok {
            continue
        }
        var e event
        if err := json.Unmarshal([]byte(payload), &e); err != nil {
            // unescaped command output, skip
            continue
        }
        fmt.Printf("[%s] %s %s\n", e.Status, e.Step, e.Message)
        if e.Status == "error" {
            failed = true
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
    if failed {
        log.Fatal("install failed")
    }
}
中文