package proxmox import ( "fmt" "strconv" "strings" ) // UPID is a parsed Proxmox task identifier. Long operations (vzdump, restore, // snapshot, ...) return a UPID rather than a result; the caller polls the task. // // Wire format (captured live, demo-felhom): // // UPID:demo-felhom:00026454:004E3431:6A265E53:vzdestroy:9021:root@pam: // |node |pid-hex |pstart-hx|start-hex |worker |id |user |(trailing) type UPID struct { Raw string Node string PID uint64 // decoded from hex PStart uint64 // decoded from hex StartTime uint64 // decoded from hex (unix seconds) Worker string // task type, e.g. "vzdump", "vzdestroy", "vzsnapshot" ID string // worker target, e.g. the vmid as a string User string // e.g. "root@pam" or "felhom-agent@pve!agent" } // ParseUPID parses a Proxmox UPID string. The user field may contain '@' and '!' // but never ':', so a plain colon-split is correct. func ParseUPID(s string) (UPID, error) { if !strings.HasPrefix(s, "UPID:") { return UPID{}, fmt.Errorf("proxmox: not a UPID: %q", s) } // UPID:node:pid:pstart:starttime:worker:id:user: -> 9 fields, last empty parts := strings.Split(s, ":") if len(parts) < 8 { return UPID{}, fmt.Errorf("proxmox: malformed UPID (%d fields): %q", len(parts), s) } pid, err := strconv.ParseUint(parts[2], 16, 64) if err != nil { return UPID{}, fmt.Errorf("proxmox: bad UPID pid %q: %w", parts[2], err) } pstart, err := strconv.ParseUint(parts[3], 16, 64) if err != nil { return UPID{}, fmt.Errorf("proxmox: bad UPID pstart %q: %w", parts[3], err) } start, err := strconv.ParseUint(parts[4], 16, 64) if err != nil { return UPID{}, fmt.Errorf("proxmox: bad UPID starttime %q: %w", parts[4], err) } return UPID{ Raw: s, Node: parts[1], PID: pid, PStart: pstart, StartTime: start, Worker: parts[5], ID: parts[6], User: parts[7], }, nil } // String returns the original wire form. func (u UPID) String() string { return u.Raw }