48df10b17e
- Harden DB-backed handlers and seed profiles; align client wire shapes for cluster resources, QEMU config, and node SSL fields - Serve plain HTTP on Compose :8006; keep TLS optional (--profile tls) and terminate HTTPS at Kubernetes Ingress - Add pulumi-tests (full contract surface majors 6–9 + BPG lifecycle) and make pulumi-tests - Ship bilingual docs, CHANGELOG, SECURITY, CONTRIBUTING, and GitHub Actions (make ci + Compose/Helm validation)
98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func env(k, def string) string {
|
|
if v := os.Getenv(k); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func main() {
|
|
base := strings.TrimRight(env("PVE_BASE", "https://localhost:8006/api2/json"), "/")
|
|
node := env("PVE_NODE", "pve01")
|
|
vmid := env("PVE_VMID", "113")
|
|
token := env("PVE_API_TOKEN", "root@pam!automation=automation-secret")
|
|
auth := "PVEAPIToken=" + token
|
|
|
|
fmt.Printf("version: %v\n", call(base+"/version", "GET", auth, nil))
|
|
upid := asString(call(base+"/nodes/"+node+"/qemu", "POST", auth, url.Values{
|
|
"vmid": {vmid},
|
|
"name": {"go-" + vmid},
|
|
"cores": {"1"},
|
|
"memory": {"512"},
|
|
}))
|
|
wait(base, node, auth, upid)
|
|
upid = asString(call(base+"/nodes/"+node+"/qemu/"+vmid+"/status/start", "POST", auth, nil))
|
|
wait(base, node, auth, upid)
|
|
fmt.Printf("status: %v\n", call(base+"/nodes/"+node+"/qemu/"+vmid+"/status/current", "GET", auth, nil))
|
|
upid = asString(call(base+"/nodes/"+node+"/qemu/"+vmid+"/status/stop", "POST", auth, nil))
|
|
wait(base, node, auth, upid)
|
|
upid = asString(call(base+"/nodes/"+node+"/qemu/"+vmid, "DELETE", auth, nil))
|
|
wait(base, node, auth, upid)
|
|
fmt.Println("ok")
|
|
}
|
|
|
|
func wait(base, node, auth, upid string) {
|
|
deadline := time.Now().Add(2 * time.Minute)
|
|
for time.Now().Before(deadline) {
|
|
status := call(base+"/nodes/"+node+"/tasks/"+url.PathEscape(upid)+"/status", "GET", auth, nil)
|
|
if m, ok := status.(map[string]any); ok {
|
|
if s, _ := m["status"].(string); s == "stopped" {
|
|
return
|
|
}
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
panic("timeout waiting for " + upid)
|
|
}
|
|
|
|
func call(u, method, auth string, values url.Values) any {
|
|
var body io.Reader
|
|
if values != nil {
|
|
body = strings.NewReader(values.Encode())
|
|
}
|
|
req, err := http.NewRequest(method, u, body)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
req.Header.Set("Authorization", auth)
|
|
if values != nil {
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
b, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode >= 300 {
|
|
panic(fmt.Sprintf("%s %s: %s", method, u, b))
|
|
}
|
|
var envelope struct {
|
|
Data any `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(b, &envelope); err != nil {
|
|
panic(err)
|
|
}
|
|
return envelope.Data
|
|
}
|
|
|
|
func asString(v any) string {
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
panic(fmt.Sprintf("expected string UPID, got %#v", v))
|
|
}
|
|
return s
|
|
}
|