f8d3cbdd59
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func env(k, def string) string {
|
|
if v := os.Getenv(k); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
var client = &http.Client{
|
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, // lab self-signed cert only
|
|
}
|
|
|
|
func main() {
|
|
base := strings.TrimRight(env("VSPHERE_BASE", "https://localhost"), "/")
|
|
user := env("VSPHERE_USER", "administrator@vsphere.local")
|
|
password := env("VSPHERE_PASSWORD", "VMware1!")
|
|
vmName := env("VSPHERE_VM_NAME", "go-lab-01")
|
|
|
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password))
|
|
var sessionID string
|
|
call(base+"/api/session", "POST", map[string]string{"Authorization": auth}, nil, &sessionID)
|
|
fmt.Println("session:", sessionID)
|
|
|
|
headers := map[string]string{"vmware-api-session-id": sessionID}
|
|
|
|
var vms []map[string]any
|
|
call(base+"/api/vcenter/vm", "GET", headers, nil, &vms)
|
|
fmt.Printf("vms before: %d\n", len(vms))
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"name": vmName,
|
|
"guest_OS": "OTHER_GUEST_64",
|
|
"placement": map[string]string{
|
|
"folder": "group-v23",
|
|
"host": "host-11",
|
|
"datastore": "datastore-31",
|
|
"resource_pool": "resgroup-22",
|
|
},
|
|
"cpu": map[string]int{"count": 1},
|
|
"memory": map[string]int{"size_MiB": 512},
|
|
})
|
|
var moid string
|
|
call(base+"/api/vcenter/vm", "POST", headers, body, &moid)
|
|
fmt.Println("created:", moid)
|
|
|
|
var power map[string]string
|
|
call(base+"/api/vcenter/vm/"+moid+"/power?action=start", "POST", headers, nil, &power)
|
|
waitTask(base, headers, power["task"])
|
|
|
|
var detail map[string]any
|
|
call(base+"/api/vcenter/vm/"+moid, "GET", headers, nil, &detail)
|
|
fmt.Println("power_state:", detail["power_state"])
|
|
|
|
call(base+"/api/vcenter/vm/"+moid+"/power?action=stop", "POST", headers, nil, &power)
|
|
waitTask(base, headers, power["task"])
|
|
|
|
call(base+"/api/vcenter/vm/"+moid, "DELETE", headers, nil, nil)
|
|
call(base+"/api/session", "DELETE", headers, nil, nil)
|
|
fmt.Println("ok")
|
|
}
|
|
|
|
func waitTask(base string, headers map[string]string, task string) {
|
|
if task == "" {
|
|
return
|
|
}
|
|
deadline := time.Now().Add(2 * time.Minute)
|
|
for time.Now().Before(deadline) {
|
|
var status map[string]any
|
|
call(base+"/api/cis/tasks/"+task, "GET", headers, nil, &status)
|
|
if s, _ := status["status"].(string); s == "SUCCEEDED" || s == "FAILED" {
|
|
return
|
|
}
|
|
time.Sleep(300 * time.Millisecond)
|
|
}
|
|
panic("timeout waiting for task " + task)
|
|
}
|
|
|
|
func call(url, method string, headers map[string]string, body []byte, out any) {
|
|
var reqBody io.Reader
|
|
if body != nil {
|
|
reqBody = strings.NewReader(string(body))
|
|
}
|
|
req, err := http.NewRequest(method, url, reqBody)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
raw, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode >= 300 {
|
|
panic(fmt.Sprintf("%s %s: %d %s", method, url, resp.StatusCode, raw))
|
|
}
|
|
if out == nil || len(raw) == 0 {
|
|
return
|
|
}
|
|
if err := json.Unmarshal(raw, out); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|