├── .gitignore ├── LICENSE ├── README.md ├── node.go ├── pool.go ├── proxmox.go ├── qemu.go ├── storage.go ├── task.go └── volume.go /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Joern Ott 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of go-proxmox nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-proxmox 2 | Proxmox API in golang. This is work in progress and far from being complete. Contributions and suggestions are welcome. 3 | 4 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | ) 8 | 9 | type Node struct { 10 | Mem float64 `json:"mem"` 11 | MaxDisk float64 `json:"maxdisk"` 12 | Node string `json:"node"` 13 | MaxCPU float64 `json:"maxcpu"` 14 | Uptime float64 `json:"uptime"` 15 | Id string `json:"id"` 16 | CPU float64 `json:"cpu"` 17 | Level string `json:"level"` 18 | NodeType string `json:"nodetype"` 19 | Disk float64 `json:"disk"` 20 | MaxMem float64 `json:"maxmem"` 21 | Proxmox ProxMox 22 | } 23 | 24 | type NodeList map[string]Node 25 | 26 | type NodeListResult struct { 27 | Data NodeList `json:"data"` 28 | } 29 | 30 | func (node Node) Qemu() (QemuList, error) { 31 | var err error 32 | var data map[string]interface{} 33 | var list QemuList 34 | var vm QemuVM 35 | var results []interface{} 36 | var VMIdFloat float64 37 | 38 | //fmt.Println("!Qemu") 39 | 40 | data, err = node.Proxmox.Get("nodes/" + node.Node + "/qemu") 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | list = make(QemuList) 46 | results = data["data"].([]interface{}) 47 | for _, v0 := range results { 48 | v := v0.(map[string]interface{}) 49 | if _, ok := v["vmid"].(string); ok { 50 | VMIdFloat, err = strconv.ParseFloat(v["vmid"].(string), 64) 51 | if err != nil { 52 | return nil, err 53 | } 54 | } else { 55 | VMIdFloat = v["vmid"].(float64) 56 | } 57 | 58 | vm = QemuVM{ 59 | Mem: v["mem"].(float64), 60 | CPUs: v["cpus"].(float64), 61 | NetOut: v["netout"].(float64), 62 | // PID: v["pid"].(string), 63 | Disk: v["disk"].(float64), 64 | MaxMem: v["maxmem"].(float64), 65 | Status: v["status"].(string), 66 | NetIn: v["netin"].(float64), 67 | MaxDisk: v["maxdisk"].(float64), 68 | Name: v["name"].(string), 69 | DiskWrite: v["diskwrite"].(float64), 70 | CPU: v["cpu"].(float64), 71 | VMId: VMIdFloat, 72 | DiskRead: v["diskread"].(float64), 73 | Uptime: v["uptime"].(float64), 74 | Node: node, 75 | } 76 | 77 | switch v["template"].(type) { 78 | case float64: 79 | vm.Template = v["template"].(float64) 80 | default: 81 | vm.Template = 0 82 | } 83 | 84 | list[strconv.FormatFloat(vm.VMId, 'f', 0, 64)] = vm 85 | } 86 | 87 | return list, nil 88 | } 89 | 90 | func (node Node) MaxQemuId() (float64, error) { 91 | var list QemuList 92 | var vm QemuVM 93 | var id float64 94 | var err error 95 | 96 | //fmt.Println("!MaxQemuId") 97 | 98 | id = 1009 99 | list, err = node.Qemu() 100 | if err != nil { 101 | return 0, err 102 | } 103 | 104 | for _, vm = range list { 105 | if vm.VMId > id && vm.VMId >= 1010 && vm.VMId < 2000 { 106 | id = vm.VMId 107 | } 108 | } 109 | return id, nil 110 | } 111 | 112 | func (node Node) Storages() (StorageList, error) { 113 | var err error 114 | var data map[string]interface{} 115 | var list StorageList 116 | var storage Storage 117 | var results []interface{} 118 | 119 | //fmt.Println("!Storages") 120 | 121 | data, err = node.Proxmox.Get("nodes/" + node.Node + "/storage") 122 | if err != nil { 123 | return nil, err 124 | } 125 | list = make(StorageList) 126 | results = data["data"].([]interface{}) 127 | for _, v0 := range results { 128 | v := v0.(map[string]interface{}) 129 | storage = Storage{ 130 | StorageType: v["type"].(string), 131 | Active: v["active"].(float64), 132 | Total: v["total"].(float64), 133 | Content: v["content"].(string), 134 | Shared: v["shared"].(float64), 135 | Storage: v["storage"].(string), 136 | Used: v["used"].(float64), 137 | Avail: v["avail"].(float64), 138 | Node: node, 139 | } 140 | list[storage.Storage] = storage 141 | } 142 | 143 | return list, nil 144 | } 145 | 146 | func (node Node) CreateQemuVM(Name string, Sockets int, Cores int, MemorySize int, DiskSize string) (string, error) { 147 | var err error 148 | var newVmId string 149 | var storageList StorageList 150 | //var storage Storage 151 | var results map[string]interface{} 152 | var storageId string 153 | var form url.Values 154 | var target string 155 | 156 | //fmt.Println("!CreateQemuVM") 157 | 158 | newVmId, err = node.Proxmox.NextVMId() 159 | if err != nil { 160 | return "", err 161 | } 162 | //fmt.Println("new VM ID: " + newVmId) 163 | storageList, err = node.Storages() 164 | results, err = storageList["local"].CreateVolume("vm-"+newVmId+"-disk-0.qcow2", DiskSize, newVmId) 165 | if err != nil { 166 | return "", err 167 | } 168 | storageId = results["data"].(string) 169 | 170 | //fmt.Println("!CreateVolume") 171 | 172 | form = url.Values{ 173 | "vmid": {newVmId}, 174 | "memory": {strconv.Itoa(MemorySize)}, 175 | "sockets": {strconv.Itoa(Sockets)}, 176 | "cores": {strconv.Itoa(Cores)}, 177 | "net0": {"virtio,bridge=vmbr0"}, 178 | "virtio0": {storageId}, 179 | } 180 | if Name != "" { 181 | form.Set("name", Name) 182 | } 183 | 184 | target = "nodes/" + node.Node + "/qemu" 185 | results, err = node.Proxmox.PostForm(target, form) 186 | if err != nil { 187 | fmt.Println("Error creating VM!!!") 188 | return "", err 189 | } 190 | //fmt.Println("VM " + newVmId + " created") 191 | 192 | return newVmId, err 193 | } 194 | 195 | func (node Node) VZDump(VmId string, BWLimit int, Compress string, IONice int, LockWait int, Mode string) (string, error) { 196 | var form url.Values 197 | var target string 198 | var err error 199 | var results map[string]interface{} 200 | 201 | form = url.Values{ 202 | "vmid": {VmId}, 203 | "compress": {Compress}, 204 | "lockwait": {strconv.Itoa(LockWait)}, 205 | "mode": {Mode}, 206 | } 207 | if BWLimit > 0 { 208 | form.Set("bwlimit", strconv.Itoa(BWLimit)) 209 | } 210 | if IONice > 0 { 211 | form.Set("ionice", strconv.Itoa(IONice)) 212 | } 213 | target = "nodes/" + node.Node + "/vzdump" 214 | results, err = node.Proxmox.PostForm(target, form) 215 | if err != nil { 216 | fmt.Println("Error dumping VM!") 217 | return "", err 218 | } 219 | return results["data"].(string), nil 220 | } 221 | 222 | func (node Node) Tasks(Limit int, Start int, UserFilter string, VmId string) (TaskList, error) { 223 | var err error 224 | var target string 225 | var data map[string]interface{} 226 | var list TaskList 227 | var task Task 228 | var results []interface{} 229 | 230 | //fmt.Println("!Tasks") 231 | target = "nodes/" + node.Node + "/tasks?" 232 | if Limit > 0 { 233 | target = target + "limit=" + strconv.Itoa(Limit) + "&" 234 | } 235 | if Start > 0 { 236 | target = target + "start=" + strconv.Itoa(Start) + "&" 237 | } 238 | if UserFilter != "" { 239 | target = target + "userfilter=" + UserFilter + "&" 240 | } 241 | if VmId != "" { 242 | target = target + "vmid=" + VmId + "&" 243 | } 244 | target = target[0 : len(target)-1] 245 | data, err = node.Proxmox.Get(target) 246 | if err != nil { 247 | return nil, err 248 | } 249 | list = make(TaskList) 250 | results = data["data"].([]interface{}) 251 | for _, v0 := range results { 252 | v := v0.(map[string]interface{}) 253 | task = Task{ 254 | UPid: v["upid"].(string), 255 | Type: v["type"].(string), 256 | Status: v["status"].(string), 257 | PID: v["pid"].(float64), 258 | PStart: v["pstart"].(float64), 259 | StartTime: v["starttime"].(float64), 260 | EndTime: v["endtime"].(float64), 261 | ID: v["id"].(string), 262 | proxmox: node.Proxmox, 263 | } 264 | list[task.UPid] = task 265 | } 266 | 267 | return list, nil 268 | } 269 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | type Pool struct { 4 | Poolid string `json:"poolid"` 5 | proxmox ProxMox 6 | } 7 | 8 | type PoolList map[string]Pool 9 | 10 | type PoolResult struct { 11 | Data Task `json:"data"` 12 | } 13 | -------------------------------------------------------------------------------- /proxmox.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "math/rand" 11 | "net/http" 12 | "net/http/cookiejar" 13 | "net/url" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | type ProxMox struct { 20 | Hostname string 21 | Username string 22 | password string 23 | VerifySSL bool 24 | BaseURL string 25 | connectionCSRFPreventionToken string 26 | ConnectionTicket string 27 | Client *http.Client 28 | } 29 | 30 | func NewProxMox(HostName string, UserName string, Password string) (*ProxMox, error) { 31 | var err error 32 | var proxmox *ProxMox 33 | var data map[string]interface{} 34 | var form url.Values 35 | var cookies []*http.Cookie 36 | var tr *http.Transport 37 | var domain string 38 | //fmt.Println("!NewProxMox") 39 | 40 | if !strings.Contains(UserName, "@") { 41 | UserName = UserName + "@pam" 42 | } 43 | 44 | if !strings.HasPrefix(HostName, "http") && !strings.HasPrefix(HostName, "https") { 45 | HostName = "https://" + HostName 46 | } 47 | 48 | proxmox = new(ProxMox) 49 | proxmox.Hostname = HostName 50 | proxmox.Username = UserName 51 | proxmox.password = Password 52 | proxmox.VerifySSL = false 53 | if len(strings.Split(proxmox.Hostname, ":")) == 1 { 54 | proxmox.BaseURL = proxmox.Hostname + ":8006" 55 | } 56 | proxmox.BaseURL = proxmox.Hostname + "/api2/json/" 57 | 58 | if proxmox.VerifySSL { 59 | tr = &http.Transport{ 60 | DisableKeepAlives: false, 61 | IdleConnTimeout: 0, 62 | MaxIdleConns: 200, 63 | MaxIdleConnsPerHost: 100} 64 | } else { 65 | tr = &http.Transport{ 66 | DisableKeepAlives: false, 67 | IdleConnTimeout: 0, 68 | MaxIdleConns: 200, 69 | MaxIdleConnsPerHost: 100, 70 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 71 | } 72 | } 73 | proxmox.Client = &http.Client{ 74 | Transport: tr, 75 | Timeout: time.Second * 10} 76 | form = url.Values{ 77 | "username": {proxmox.Username}, 78 | "password": {proxmox.password}, 79 | } 80 | 81 | data, err = proxmox.PostForm("access/ticket", form) 82 | if err != nil { 83 | return nil, err 84 | } else { 85 | proxmox.ConnectionTicket = data["ticket"].(string) 86 | proxmox.connectionCSRFPreventionToken = data["CSRFPreventionToken"].(string) 87 | proxmox.Client.Jar, err = cookiejar.New(nil) 88 | domain = proxmox.Hostname 89 | 90 | cookie := &http.Cookie{ 91 | Name: "PVEAuthCookie", 92 | Value: data["ticket"].(string), 93 | Path: "/", 94 | } 95 | cookies = append(cookies, cookie) 96 | cookieURL, err := url.Parse(domain + "/") 97 | if err != nil { 98 | return nil, err 99 | } 100 | proxmox.Client.Jar.SetCookies(cookieURL, cookies) 101 | 102 | return proxmox, nil 103 | } 104 | } 105 | 106 | func (proxmox ProxMox) Nodes() (NodeList, error) { 107 | var err error 108 | var data map[string]interface{} 109 | var list NodeList 110 | var node Node 111 | var results []interface{} 112 | 113 | //fmt.Println("!Nodes") 114 | 115 | data, err = proxmox.Get("nodes") 116 | if err != nil { 117 | return nil, err 118 | } 119 | list = make(NodeList) 120 | results = data["data"].([]interface{}) 121 | for _, v0 := range results { 122 | v := v0.(map[string]interface{}) 123 | if val, ok := v["uptime"]; !ok || val == nil { 124 | fmt.Println("Node probably down. Skipping.") 125 | continue 126 | } 127 | node = Node{ 128 | Mem: v["mem"].(float64), 129 | MaxDisk: v["maxdisk"].(float64), 130 | Node: v["node"].(string), 131 | MaxCPU: v["maxcpu"].(float64), 132 | Uptime: v["uptime"].(float64), 133 | Id: v["id"].(string), 134 | CPU: v["cpu"].(float64), 135 | Level: v["level"].(string), 136 | NodeType: v["type"].(string), 137 | Disk: v["disk"].(float64), 138 | MaxMem: v["maxmem"].(float64), 139 | Proxmox: proxmox, 140 | } 141 | list[node.Node] = node 142 | } 143 | return list, nil 144 | } 145 | 146 | func (proxmox ProxMox) NextVMId() (string, error) { 147 | data, err := proxmox.Get("cluster/nextid") 148 | if err != nil { 149 | return "", err 150 | } 151 | 152 | result := data["data"].(string) 153 | return result, nil 154 | } 155 | 156 | func (proxmox ProxMox) DetermineVMPlacement(cpu int64, cores int64, mem int64, overCommitCPU float64, overCommitMem float64) (Node, error) { 157 | var nodeList NodeList 158 | var node Node 159 | var qemuList []QemuVM 160 | var qemu QemuVM 161 | var errNode Node 162 | var usedCPUs int64 163 | var usedMem int64 164 | 165 | var err error 166 | 167 | nodeList, err = proxmox.Nodes() 168 | if err != nil { 169 | return errNode, errors.New("Could not get any nodes.") 170 | } 171 | for _, node = range nodeList { 172 | qemuListSorted, err := node.Qemu() 173 | if err != nil { 174 | return errNode, errors.New("Could not get VMs for node " + node.Node + ".") 175 | } 176 | //Randomize order of nodes 177 | qemuList = make([]QemuVM, len(qemuListSorted)) 178 | perm := rand.Perm(len(qemuListSorted)) 179 | i := 0 180 | for s := range qemuListSorted { 181 | qemuList[perm[i]] = qemuListSorted[s] 182 | i++ 183 | } 184 | for _, qemu = range qemuList { 185 | usedCPUs = usedCPUs + int64(qemu.CPUs) 186 | usedMem = usedMem + int64(qemu.MaxMem) 187 | } 188 | if (cpu*cores < int64(node.MaxCPU*(1+overCommitCPU))-usedCPUs) && (mem < int64(node.MaxMem*(1+overCommitMem))-usedMem) { 189 | return node, nil 190 | // } else { 191 | // fmt.Printf("CPU: %v < %v, Memory: %v < %v\n", cpu*cores, int64(node.MaxCPU*(1+overCommitCPU))-usedCPUs, mem, int64(node.MaxMem*(1+overCommitMem))-usedMem) 192 | } 193 | } 194 | return errNode, errors.New("Not enough free capacity on any of the nodes.") 195 | } 196 | 197 | func (proxmox ProxMox) FindVM(VmId string) (QemuVM, error) { 198 | var nodeList NodeList 199 | var node Node 200 | var qemuList QemuList 201 | var qemu QemuVM 202 | var errQemu QemuVM 203 | var ok bool 204 | var err error 205 | 206 | nodeList, err = proxmox.Nodes() 207 | if err != nil { 208 | return errQemu, errors.New("Could not get any nodes.") 209 | } 210 | for _, node = range nodeList { 211 | qemuList, err = node.Qemu() 212 | if err != nil { 213 | return errQemu, errors.New("Could not get VMs for node " + node.Node + ".") 214 | } 215 | if qemu, ok = qemuList[VmId]; ok { 216 | return qemu, nil 217 | } 218 | } 219 | return errQemu, errors.New("VM " + VmId + " not found.") 220 | } 221 | 222 | func (proxmox ProxMox) Tasks() (TaskList, error) { 223 | var err error 224 | var target string 225 | var data map[string]interface{} 226 | var list TaskList 227 | var task Task 228 | var results []interface{} 229 | 230 | //fmt.Println("!Tasks") 231 | target = "cluster/tasks" 232 | data, err = proxmox.Get(target) 233 | if err != nil { 234 | return nil, err 235 | } 236 | list = make(TaskList) 237 | results = data["data"].([]interface{}) 238 | for _, v0 := range results { 239 | v := v0.(map[string]interface{}) 240 | task = Task{ 241 | UPid: v["upid"].(string), 242 | Type: v["type"].(string), 243 | ID: v["id"].(string), 244 | proxmox: proxmox, 245 | } 246 | switch v["status"].(type) { 247 | default: 248 | task.Status = "" 249 | case string: 250 | task.Status = v["status"].(string) 251 | } 252 | switch v["exitstatus"].(type) { 253 | default: 254 | task.ExitStatus = "" 255 | case string: 256 | task.ExitStatus = v["exitstatus"].(string) 257 | } 258 | switch v["pstart"].(type) { 259 | default: 260 | task.PStart = 0 261 | case float64: 262 | task.PStart = v["pstart"].(float64) 263 | } 264 | switch v["starttime"].(type) { 265 | default: 266 | task.StartTime = 0 267 | case float64: 268 | task.StartTime = v["starttime"].(float64) 269 | case string: 270 | s := v["starttime"].(string) 271 | task.StartTime, err = strconv.ParseFloat(s, 64) 272 | } 273 | switch v["endtime"].(type) { 274 | default: 275 | task.EndTime = 0 276 | case float64: 277 | task.EndTime = v["endtime"].(float64) 278 | case string: 279 | s := v["endtime"].(string) 280 | task.EndTime, err = strconv.ParseFloat(s, 64) 281 | } 282 | switch v["pid"].(type) { 283 | default: 284 | task.PID = 0 285 | case float64: 286 | task.PID = v["pid"].(float64) 287 | } 288 | 289 | list[task.UPid] = task 290 | } 291 | 292 | return list, nil 293 | } 294 | 295 | func (proxmox ProxMox) Pools() (PoolList, error) { 296 | var err error 297 | var target string 298 | var data map[string]interface{} 299 | var list PoolList 300 | var pool Pool 301 | var results []interface{} 302 | 303 | //fmt.Println("!Tasks") 304 | target = "pools" 305 | data, err = proxmox.Get(target) 306 | if err != nil { 307 | return nil, err 308 | } 309 | list = make(PoolList) 310 | results = data["data"].([]interface{}) 311 | for _, v0 := range results { 312 | v := v0.(map[string]interface{}) 313 | pool = Pool{ 314 | Poolid: v["poolid"].(string), 315 | proxmox: proxmox, 316 | } 317 | 318 | list[pool.Poolid] = pool 319 | } 320 | 321 | return list, nil 322 | } 323 | 324 | func (proxmox ProxMox) NewPool(name string, comment string) (map[string]interface{}, error) { 325 | poolForm := url.Values{} 326 | poolForm.Set("poolid", name) 327 | poolForm.Set("comment", comment) 328 | 329 | result, err := proxmox.PostForm("pools", poolForm) 330 | if err != nil { 331 | fmt.Println("Error while posting form") 332 | fmt.Println(err) 333 | return result, err 334 | } 335 | 336 | fmt.Printf("Result: %s", result) 337 | 338 | return result, nil 339 | } 340 | 341 | func (proxmox ProxMox) UpdatePool(name string, comment string) (map[string]interface{}, error) { 342 | poolForm := url.Values{} 343 | poolForm.Set("poolid", name) 344 | poolForm.Set("comment", comment) 345 | 346 | result, err := proxmox.PutForm("pools/"+name, poolForm) 347 | if err != nil { 348 | fmt.Println("Error while posting form") 349 | fmt.Println(err) 350 | return result, err 351 | } 352 | 353 | fmt.Printf("Result: %s", result) 354 | 355 | return result, nil 356 | } 357 | 358 | func (proxmox ProxMox) DeletePool(name string) error { 359 | result, err := proxmox.Delete(fmt.Sprintf("pools/%s", name)) 360 | 361 | if err != nil { 362 | fmt.Printf("Error deleting pool: %s", name) 363 | fmt.Println(err) 364 | fmt.Printf("Result was: %s", result) 365 | } 366 | 367 | fmt.Printf("Created pool: %s\n", name) 368 | 369 | return nil 370 | } 371 | 372 | func (proxmox ProxMox) PostForm(endpoint string, form url.Values) (map[string]interface{}, error) { 373 | var target string 374 | var data interface{} 375 | var req *http.Request 376 | 377 | //fmt.Println("!PostForm") 378 | 379 | target = proxmox.BaseURL + endpoint 380 | //target = "http://requestb.in/1ls8s9d1" 381 | //fmt.Println("POST form " + target) 382 | 383 | req, err := http.NewRequest("POST", target, bytes.NewBufferString(form.Encode())) 384 | 385 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 386 | req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) 387 | if proxmox.connectionCSRFPreventionToken != "" { 388 | req.Header.Add("CSRFPreventionToken", proxmox.connectionCSRFPreventionToken) 389 | } 390 | 391 | //fmt.Printf("Posting form values: %s\n", req) 392 | 393 | r, err := proxmox.Client.Do(req) 394 | if err != nil { 395 | fmt.Println("Error while posting") 396 | fmt.Println(err) 397 | return nil, err 398 | } 399 | //fmt.Print("HTTP status ") 400 | //fmt.Println(r.StatusCode) 401 | if r.StatusCode != 200 { 402 | return nil, errors.New("HTTP Error " + r.Status) 403 | // } else { 404 | } 405 | 406 | response, err := ioutil.ReadAll(r.Body) 407 | r.Body.Close() 408 | if err != nil { 409 | fmt.Println("Error while reading body") 410 | fmt.Println(err) 411 | return nil, err 412 | } 413 | err = json.Unmarshal(response, &data) 414 | if err != nil { 415 | fmt.Println("Error while processing JSON") 416 | fmt.Println(err) 417 | return nil, err 418 | } 419 | m := data.(map[string]interface{}) 420 | switch m["data"].(type) { 421 | case map[string]interface{}: 422 | d := m["data"].(map[string]interface{}) 423 | return d, nil 424 | } 425 | return m, nil 426 | } 427 | 428 | func (proxmox ProxMox) Post(endpoint string, input string) (map[string]interface{}, error) { 429 | var target string 430 | var data interface{} 431 | var req *http.Request 432 | 433 | //fmt.Println("!Post") 434 | 435 | target = proxmox.BaseURL + endpoint 436 | //target = "http://requestb.in/1ls8s9d1" 437 | //fmt.Println("POST form " + target) 438 | 439 | req, err := http.NewRequest("POST", target, bytes.NewBufferString(input)) 440 | 441 | req.Header.Add("Content-Length", strconv.Itoa(len(input))) 442 | if proxmox.connectionCSRFPreventionToken != "" { 443 | req.Header.Add("CSRFPreventionToken", proxmox.connectionCSRFPreventionToken) 444 | } 445 | r, err := proxmox.Client.Do(req) 446 | if err != nil { 447 | fmt.Println("Error while posting") 448 | fmt.Println(err) 449 | return nil, err 450 | } 451 | //fmt.Print("HTTP status ") 452 | //fmt.Println(r.StatusCode) 453 | if r.StatusCode != 200 { 454 | return nil, errors.New("HTTP Error " + r.Status) 455 | // } else { 456 | } 457 | 458 | response, err := ioutil.ReadAll(r.Body) 459 | r.Body.Close() 460 | if err != nil { 461 | fmt.Println("Error while reading body") 462 | fmt.Println(err) 463 | return nil, err 464 | } 465 | err = json.Unmarshal(response, &data) 466 | if err != nil { 467 | fmt.Println("Error while processing JSON") 468 | fmt.Println(err) 469 | return nil, err 470 | } 471 | m := data.(map[string]interface{}) 472 | switch m["data"].(type) { 473 | case map[string]interface{}: 474 | d := m["data"].(map[string]interface{}) 475 | return d, nil 476 | } 477 | return m, nil 478 | } 479 | 480 | func (proxmox ProxMox) PutForm(endpoint string, form url.Values) (map[string]interface{}, error) { 481 | var target string 482 | var data interface{} 483 | var req *http.Request 484 | 485 | //fmt.Println("!PutForm") 486 | 487 | target = proxmox.BaseURL + endpoint 488 | 489 | req, err := http.NewRequest("PUT", target, bytes.NewBufferString(form.Encode())) 490 | 491 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 492 | req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode()))) 493 | if proxmox.connectionCSRFPreventionToken != "" { 494 | req.Header.Add("CSRFPreventionToken", proxmox.connectionCSRFPreventionToken) 495 | } 496 | r, err := proxmox.Client.Do(req) 497 | defer r.Body.Close() 498 | if err != nil { 499 | fmt.Println("Error while puting") 500 | fmt.Println(err) 501 | return nil, err 502 | } 503 | //fmt.Print("HTTP status ") 504 | //fmt.Println(r.StatusCode) 505 | if r.StatusCode != 200 { 506 | //spew.Dump(r) 507 | return nil, errors.New("HTTP Error " + r.Status) 508 | // } else { 509 | // spew.Dump(r) 510 | } 511 | 512 | response, err := ioutil.ReadAll(r.Body) 513 | if err != nil { 514 | fmt.Println("Error while reading body") 515 | fmt.Println(err) 516 | return nil, err 517 | } 518 | err = json.Unmarshal(response, &data) 519 | if err != nil { 520 | fmt.Println("Error while processing JSON") 521 | fmt.Println(err) 522 | return nil, err 523 | } 524 | m := data.(map[string]interface{}) 525 | //spew.Dump(m) 526 | switch m["data"].(type) { 527 | case map[string]interface{}: 528 | d := m["data"].(map[string]interface{}) 529 | return d, nil 530 | } 531 | return m, nil 532 | } 533 | 534 | func (proxmox ProxMox) GetRaw(endpoint string) ([]byte, error) { 535 | target := proxmox.BaseURL + endpoint 536 | r, err := proxmox.Client.Get(target) 537 | if err != nil { 538 | return nil, err 539 | } 540 | response, err := ioutil.ReadAll(r.Body) 541 | r.Body.Close() 542 | if err != nil { 543 | return nil, err 544 | } 545 | 546 | return response, nil 547 | } 548 | 549 | func (proxmox ProxMox) Get(endpoint string) (map[string]interface{}, error) { 550 | var target string 551 | var data interface{} 552 | 553 | //fmt.Println("!get") 554 | 555 | target = proxmox.BaseURL + endpoint 556 | //target = "http://requestb.in/1ls8s9d1" 557 | //fmt.Println("GET " + target) 558 | r, err := proxmox.Client.Get(target) 559 | if err != nil { 560 | return nil, err 561 | } 562 | response, err := ioutil.ReadAll(r.Body) 563 | r.Body.Close() 564 | if err != nil { 565 | return nil, err 566 | } 567 | err = json.Unmarshal(response, &data) 568 | if err != nil { 569 | return nil, err 570 | } 571 | m := data.(map[string]interface{}) 572 | //d := m["data"].(map[string]interface{}) 573 | return m, nil 574 | } 575 | 576 | func (proxmox ProxMox) GetBytes(endpoint string) ([]byte, error) { 577 | var target string 578 | 579 | //fmt.Println("!getBytes") 580 | 581 | target = proxmox.BaseURL + endpoint 582 | //target = "http://requestb.in/1ls8s9d1" 583 | //fmt.Println("GET " + target) 584 | r, err := proxmox.Client.Get(target) 585 | if err != nil { 586 | return nil, err 587 | } 588 | response, err := ioutil.ReadAll(r.Body) 589 | r.Body.Close() 590 | if err != nil { 591 | return nil, err 592 | } 593 | return response, nil 594 | } 595 | 596 | func (proxmox ProxMox) Delete(endpoint string) (map[string]interface{}, error) { 597 | var target string 598 | var data interface{} 599 | var req *http.Request 600 | 601 | //fmt.Println("!PostForm") 602 | 603 | target = proxmox.BaseURL + endpoint 604 | //target = "http://requestb.in/1ls8s9d1" 605 | //fmt.Println("DELETE " + target) 606 | 607 | req, err := http.NewRequest("DELETE", target, nil) 608 | 609 | req.Header.Add("CSRFPreventionToken", proxmox.connectionCSRFPreventionToken) 610 | 611 | r, err := proxmox.Client.Do(req) 612 | if err != nil { 613 | fmt.Println("Error while deleting") 614 | fmt.Println(err) 615 | return nil, err 616 | } 617 | //fmt.Print("HTTP status ") 618 | //fmt.Println(r.StatusCode) 619 | if r.StatusCode != 200 { 620 | return nil, errors.New("HTTP Error " + r.Status) 621 | // } else { 622 | } 623 | 624 | response, err := ioutil.ReadAll(r.Body) 625 | r.Body.Close() 626 | if err != nil { 627 | fmt.Println("Error while reading body") 628 | fmt.Println(err) 629 | return nil, err 630 | } 631 | err = json.Unmarshal(response, &data) 632 | if err != nil { 633 | fmt.Println("Error while processing JSON") 634 | fmt.Println(err) 635 | return nil, err 636 | } 637 | m := data.(map[string]interface{}) 638 | switch m["data"].(type) { 639 | case map[string]interface{}: 640 | d := m["data"].(map[string]interface{}) 641 | return d, nil 642 | } 643 | return m, nil 644 | } 645 | -------------------------------------------------------------------------------- /qemu.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type QemuVM struct { 12 | Mem float64 `json:"mem"` 13 | CPUs float64 `json:"cpus"` 14 | NetOut float64 `json:"netout"` 15 | PID string `json:"pid"` 16 | Disk float64 `json:"disk"` 17 | MaxMem float64 `json:"maxmem"` 18 | Status string `json:"status"` 19 | Template float64 `json:"template"` 20 | NetIn float64 `json:"netin"` 21 | MaxDisk float64 `json:"maxdisk"` 22 | Name string `json:"name"` 23 | DiskWrite float64 `json:"diskwrite"` 24 | CPU float64 `json:"cpu"` 25 | VMId float64 `json:"vmid"` 26 | DiskRead float64 `json:"diskread"` 27 | Uptime float64 `json:"uptime"` 28 | Node Node 29 | } 30 | 31 | type QemuList map[string]QemuVM 32 | 33 | type QemuNet map[string]string 34 | 35 | type QemuConfig struct { 36 | Bootdisk string `json:"bootdisk"` 37 | Cores float64 `json:"cores"` 38 | Digest string `json:"digest"` 39 | Memory float64 `json:"memory"` 40 | Net map[string]QemuNet 41 | SMBios1 string `json:"smbios1"` 42 | Sockets float64 `json:"sockets"` 43 | Disks map[string]string `json:"disks"` 44 | Description string `json:"description"` 45 | } 46 | 47 | type QemuStatus struct { 48 | CPU float64 `json:"cpu"` 49 | CPUs float64 `json:"cpus"` 50 | Mem float64 `json:"mem"` 51 | MaxMem float64 `json:"maxmem"` 52 | Disk float64 `json:"disk"` 53 | MaxDisk float64 `json:"maxdisk"` 54 | DiskWrite float64 `json:"diskwrite"` 55 | DiskRead float64 `json:"diskread"` 56 | NetIn float64 `json:"netin"` 57 | NetOut float64 `json:"netout"` 58 | Uptime float64 `json:"uptime"` 59 | QmpStatus string `json:"qmpstatus"` 60 | Status string `json:"status"` 61 | Template string `json:"template"` 62 | } 63 | 64 | func (qemu QemuVM) Delete() (map[string]interface{}, error) { 65 | var target string 66 | var data map[string]interface{} 67 | var err error 68 | 69 | //fmt.Print("!QemuDelete ", qemu.VMId) 70 | 71 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) 72 | data, err = qemu.Node.Proxmox.Delete(target) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return data, nil 77 | } 78 | 79 | func stringToMap(data string, itemSeparator string, kvSeparator string) map[string]string { 80 | var result map[string]string 81 | 82 | result = make(map[string]string) 83 | list := strings.Split(data, itemSeparator) 84 | for _, item := range list { 85 | kv := strings.Split(item, kvSeparator) 86 | result[kv[0]] = kv[1] 87 | } 88 | return result 89 | } 90 | 91 | func (qemu QemuVM) Config() (QemuConfig, error) { 92 | var target string 93 | var data map[string]interface{} 94 | var results map[string]interface{} 95 | var config QemuConfig 96 | var err error 97 | var description string 98 | 99 | //fmt.Print("!QemuConfig ", qemu.VMId) 100 | 101 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config" 102 | data, err = qemu.Node.Proxmox.Get(target) 103 | results = data["data"].(map[string]interface{}) 104 | if err != nil { 105 | return config, err 106 | } 107 | if description, _ = results["description"].(string); err != nil { 108 | return config, err 109 | } 110 | config = QemuConfig{ 111 | Bootdisk: results["bootdisk"].(string), 112 | Cores: results["cores"].(float64), 113 | Digest: results["digest"].(string), 114 | Memory: results["memory"].(float64), 115 | Sockets: results["sockets"].(float64), 116 | SMBios1: results["smbios1"].(string), 117 | Description: description, 118 | } 119 | 120 | switch results["cores"].(type) { 121 | default: 122 | config.Cores = 1 123 | case float64: 124 | config.Cores = results["cores"].(float64) 125 | } 126 | switch results["sockets"].(type) { 127 | default: 128 | config.Sockets = 1 129 | case float64: 130 | config.Sockets = results["sockets"].(float64) 131 | } 132 | disktype := [4]string{"virtio", "sata", "ide", "scsi"} 133 | disknum := [4]string{"0", "1", "2", "3"} 134 | config.Disks = make(map[string]string) 135 | for _, d := range disktype { 136 | for _, i := range disknum { 137 | id := d + i 138 | if disk, ok := results[id]; ok { 139 | config.Disks[id] = disk.(string) 140 | } 141 | } 142 | } 143 | config.Net = make(map[string]QemuNet) 144 | netnum := [4]string{"0", "1", "2", "3"} 145 | for _, n := range netnum { 146 | if net, ok := results["net"+n]; ok { 147 | config.Net["net"+n] = stringToMap(net.(string), ",", "=") 148 | } 149 | } 150 | 151 | return config, nil 152 | } 153 | 154 | func (qemu QemuVM) CurrentStatus() (QemuStatus, error) { 155 | var target string 156 | var err error 157 | var data map[string]interface{} 158 | var results map[string]interface{} 159 | var status QemuStatus 160 | 161 | //fmt.Println("!QemuStatus ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64)) 162 | 163 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/current" 164 | data, err = qemu.Node.Proxmox.Get(target) 165 | if err != nil { 166 | return status, err 167 | } 168 | results = data["data"].(map[string]interface{}) 169 | status = QemuStatus{ 170 | CPU: results["cpu"].(float64), 171 | CPUs: results["cpus"].(float64), 172 | Mem: results["mem"].(float64), 173 | MaxMem: results["maxmem"].(float64), 174 | Disk: results["disk"].(float64), 175 | MaxDisk: results["maxdisk"].(float64), 176 | DiskWrite: results["diskwrite"].(float64), 177 | DiskRead: results["diskread"].(float64), 178 | NetIn: results["netin"].(float64), 179 | NetOut: results["netout"].(float64), 180 | Uptime: results["uptime"].(float64), 181 | QmpStatus: results["qmpstatus"].(string), 182 | Status: results["status"].(string), 183 | Template: results["template"].(string), 184 | } 185 | return status, nil 186 | } 187 | 188 | func (qemu QemuVM) WaitForStatus(status string, timeout int) error { 189 | var i int 190 | var err error 191 | var qStatus QemuStatus 192 | for i = 0; i < timeout; i++ { 193 | qStatus, err = qemu.CurrentStatus() 194 | if err != nil { 195 | return err 196 | } 197 | 198 | if qStatus.Status == status { 199 | return nil 200 | } 201 | time.Sleep(time.Second * 1) 202 | } 203 | return errors.New("Timeout reached") 204 | } 205 | 206 | func (qemu QemuVM) Start() error { 207 | var target string 208 | var err error 209 | 210 | //fmt.Println("!QemuStart ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64)) 211 | 212 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/start" 213 | _, err = qemu.Node.Proxmox.Post(target, "") 214 | return err 215 | } 216 | 217 | func (qemu QemuVM) Stop() (string, error) { 218 | var target string 219 | var err error 220 | 221 | //fmt.Print("!QemuStop ", qemu.VMId) 222 | 223 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/stop" 224 | data, err := qemu.Node.Proxmox.Post(target, "") 225 | if err != nil { 226 | return "", err 227 | } 228 | 229 | UPid := data["data"].(string) 230 | 231 | return UPid, nil 232 | } 233 | 234 | func (qemu QemuVM) Shutdown() (Task, error) { 235 | var target string 236 | var err error 237 | 238 | //fmt.Print("!QemuShutdown ", qemu.VMId) 239 | 240 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/shutdown" 241 | data, err := qemu.Node.Proxmox.Post(target, "") 242 | 243 | if err != err { 244 | return Task{}, err 245 | } 246 | 247 | t := Task{ 248 | UPid: data["data"].(string), 249 | proxmox: qemu.Node.Proxmox, 250 | } 251 | 252 | return t, err 253 | } 254 | 255 | func (qemu QemuVM) Suspend() error { 256 | var target string 257 | var err error 258 | 259 | //fmt.Print("!QemuSuspend ", qemu.VMId) 260 | 261 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/suspend" 262 | _, err = qemu.Node.Proxmox.Post(target, "") 263 | return err 264 | } 265 | 266 | func (qemu QemuVM) Resume() error { 267 | var target string 268 | var err error 269 | 270 | //fmt.Print("!QemuResume ", qemu.VMId) 271 | 272 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/resume" 273 | _, err = qemu.Node.Proxmox.Post(target, "") 274 | return err 275 | } 276 | 277 | func (qemu QemuVM) Clone(newId float64, name string, targetName string) (Task, error) { 278 | return qemu.CloneToPool(newId, name, targetName, "") 279 | } 280 | 281 | func (qemu QemuVM) CloneToPool(newId float64, name string, targetName string, pool string) (Task, error) { 282 | var target string 283 | var err error 284 | 285 | newVMID := strconv.FormatFloat(newId, 'f', 0, 64) 286 | 287 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/clone" 288 | 289 | form := url.Values{ 290 | "newid": {newVMID}, 291 | "name": {name}, 292 | "target": {targetName}, 293 | "full": {"1"}, 294 | } 295 | 296 | if pool != "" { 297 | form.Add("pool", pool) 298 | } 299 | 300 | data, err := qemu.Node.Proxmox.PostForm(target, form) 301 | if err != err { 302 | return Task{}, err 303 | } 304 | 305 | t := Task{ 306 | UPid: data["data"].(string), 307 | proxmox: qemu.Node.Proxmox, 308 | } 309 | 310 | return t, nil 311 | } 312 | 313 | func (qemu QemuVM) SetDescription(description string) error { 314 | var target string 315 | var err error 316 | 317 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config" 318 | 319 | form := url.Values{ 320 | "description": {description}, 321 | } 322 | 323 | _, err = qemu.Node.Proxmox.PutForm(target, form) 324 | if err != err { 325 | return err 326 | } 327 | 328 | return nil 329 | } 330 | 331 | func (qemu QemuVM) SetMemory(memory string) error { 332 | var target string 333 | var err error 334 | 335 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config" 336 | 337 | form := url.Values{ 338 | "memory": {memory}, 339 | } 340 | 341 | _, err = qemu.Node.Proxmox.PutForm(target, form) 342 | if err != err { 343 | return err 344 | } 345 | 346 | return nil 347 | } 348 | 349 | func (qemu QemuVM) SetIPSet(ip string) error { 350 | var target string 351 | var err error 352 | 353 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/options" 354 | 355 | form := url.Values{ 356 | "dhcp": {"1"}, 357 | "enable": {"1"}, 358 | "log_level_in": {"nolog"}, 359 | "log_level_out": {"nolog"}, 360 | "macfilter": {"1"}, 361 | "ipfilter": {"1"}, 362 | "ndp": {"1"}, 363 | "policy_in": {"ACCEPT"}, 364 | "policy_out": {"ACCEPT"}, 365 | } 366 | 367 | _, err = qemu.Node.Proxmox.PutForm(target, form) 368 | if err != nil { 369 | return err 370 | } 371 | 372 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset" 373 | 374 | form = url.Values{ 375 | "name": {"ipfilter-net0"}, 376 | } 377 | 378 | _, err = qemu.Node.Proxmox.PostForm(target, form) 379 | if err != nil { 380 | return err 381 | } 382 | 383 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset/ipfilter-net0" 384 | 385 | form = url.Values{ 386 | "cidr": {ip}, 387 | } 388 | 389 | _, err = qemu.Node.Proxmox.PostForm(target, form) 390 | if err != nil { 391 | return err 392 | } 393 | 394 | config, err := qemu.Config() 395 | if err != nil { 396 | return err 397 | } 398 | 399 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config" 400 | 401 | net := "" 402 | 403 | for k, v := range config.Net["net0"] { 404 | net += k + "=" + v + "," 405 | } 406 | 407 | form = url.Values{ 408 | "net0": {net + ",firewall=1"}, 409 | } 410 | 411 | _, err = qemu.Node.Proxmox.PutForm(target, form) 412 | if err != nil { 413 | return err 414 | } 415 | 416 | return nil 417 | } 418 | 419 | func (qemu QemuVM) ResizeDisk(size string) error { 420 | var target string 421 | var err error 422 | 423 | target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/resize" 424 | 425 | form := url.Values{ 426 | "disk": {"scsi1"}, 427 | "size": {size + "G"}, 428 | } 429 | 430 | _, err = qemu.Node.Proxmox.PutForm(target, form) 431 | if err != nil { 432 | return err 433 | } 434 | 435 | return nil 436 | } 437 | 438 | func (qemu QemuVM) Snapshot(name string, includeRAM bool) (string, error) { 439 | target := "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/snapshot" 440 | 441 | vmstate := "0" 442 | if includeRAM == true { 443 | vmstate = "1" 444 | } 445 | 446 | form := url.Values{ 447 | "snapname": {name}, 448 | "vmstate": {vmstate}, 449 | } 450 | 451 | data, err := qemu.Node.Proxmox.PostForm(target, form) 452 | if err != nil { 453 | return "", err 454 | } 455 | 456 | UPid := data["data"].(string) 457 | 458 | return UPid, nil 459 | } 460 | 461 | func (qemu QemuVM) Rollback(name string) (string, error) { 462 | target := "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/snapshot/" + name + "/rollback" 463 | 464 | data, err := qemu.Node.Proxmox.Post(target, "") 465 | if err != nil { 466 | return "", err 467 | } 468 | 469 | UPid := data["data"].(string) 470 | 471 | return UPid, nil 472 | } 473 | -------------------------------------------------------------------------------- /storage.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | type Storage struct { 9 | StorageType string `json:"type"` 10 | Active float64 `json:"active"` 11 | Total float64 `json:"total"` 12 | Content string `json:"content"` 13 | Shared float64 `json:"shared"` 14 | Storage string `json:"storage"` 15 | Used float64 `json:"used"` 16 | Avail float64 `json:"avail"` 17 | Node Node 18 | } 19 | 20 | type StorageList map[string]Storage 21 | 22 | func (storage Storage) CreateVolume(FileName string, DiskSize string, VmId string) (map[string]interface{}, error) { 23 | var form url.Values 24 | var err error 25 | var data map[string]interface{} 26 | var target string 27 | 28 | //fmt.Println("!CreateVolume") 29 | 30 | form = url.Values{ 31 | "filename": {FileName}, 32 | // "node": {storage.node.Node}, 33 | "size": {DiskSize}, 34 | "format": {"qcow2"}, 35 | "vmid": {VmId}, 36 | } 37 | 38 | target = "nodes/" + storage.Node.Node + "/storage/" + storage.Storage + "/content" 39 | data, err = storage.Node.Proxmox.PostForm(target, form) 40 | if err != nil { 41 | fmt.Println("Error!!!") 42 | return nil, err 43 | } 44 | //fmt.Println("Storage created") 45 | return data, err 46 | } 47 | 48 | func (storage Storage) Volumes() (VolumeList, error) { 49 | var err error 50 | var target string 51 | var data map[string]interface{} 52 | var list VolumeList 53 | var volume Volume 54 | var results []interface{} 55 | 56 | //fmt.Println("!Volumes") 57 | 58 | target = "nodes/" + storage.Node.Node + "/storage/" + storage.Storage + "/content" 59 | data, err = storage.Node.Proxmox.Get(target) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | list = make(VolumeList) 65 | results = data["data"].([]interface{}) 66 | for _, v0 := range results { 67 | v := v0.(map[string]interface{}) 68 | volume = Volume{ 69 | Size: v["size"].(float64), 70 | VolId: v["volid"].(string), 71 | VmId: v["vmid"].(string), 72 | Format: v["format"].(string), 73 | Content: v["content"].(string), 74 | Used: v["used"].(float64), 75 | storage: storage, 76 | } 77 | list[volume.VolId] = volume 78 | } 79 | return list, nil 80 | } 81 | -------------------------------------------------------------------------------- /task.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type Task struct { 11 | UPid string `json:"upid"` 12 | Type string `json:"type"` 13 | Status string `json:"status"` 14 | ExitStatus string `json:"exitstatus"` 15 | PID float64 `json:"pid"` 16 | PStart float64 `json:"pstart"` 17 | StartTime float64 `json:"starttime"` 18 | EndTime float64 `json:"endtime"` 19 | ID string `json:"id"` 20 | proxmox ProxMox 21 | } 22 | 23 | type TaskList map[string]Task 24 | 25 | type TaskResult struct { 26 | Data Task `json:"data"` 27 | } 28 | 29 | func (task Task) GetStatus() (string, string, error) { 30 | var target string 31 | var err error 32 | var raw []byte 33 | var data TaskResult 34 | 35 | upidParts := strings.Split(task.UPid, ":") 36 | target = "nodes/" + upidParts[1] + "/tasks/" + task.UPid + "/status" 37 | //fmt.Println("target " + target) 38 | raw, err = task.proxmox.GetBytes(target) 39 | if err != nil { 40 | return "", "", err 41 | } 42 | err = json.Unmarshal(raw, &data) 43 | if err != nil { 44 | return "", "", err 45 | } 46 | return data.Data.Status, data.Data.ExitStatus, nil 47 | } 48 | 49 | func (task Task) WaitForStatus(status string, timeout int) (string, error) { 50 | var i int 51 | var err error 52 | var actstatus string 53 | var exitstatus string 54 | for i = 0; i < timeout; i++ { 55 | actstatus, exitstatus, err = task.GetStatus() 56 | if err != nil { 57 | return "", err 58 | } 59 | if actstatus == status { 60 | return exitstatus, nil 61 | } 62 | time.Sleep(time.Second * 1) 63 | } 64 | return "", errors.New("Timeout reached") 65 | } 66 | -------------------------------------------------------------------------------- /volume.go: -------------------------------------------------------------------------------- 1 | package proxmox 2 | 3 | type Volume struct { 4 | Size float64 5 | VolId string 6 | VmId string 7 | Format string 8 | Content string 9 | Used float64 10 | storage Storage 11 | } 12 | 13 | type VolumeList map[string]Volume 14 | --------------------------------------------------------------------------------