From b762646338d9854e9aa86ebab7ac5b167bc49bbd Mon Sep 17 00:00:00 2001 From: "Christopher A. Baumbauer" Date: Mon, 25 Apr 2016 21:26:26 -0700 Subject: [PATCH] Added egoscale to Godeps --- Godeps/Godeps.json | 4 + .../src/github.com/runseb/egoscale/.gitignore | 2 + .../src/github.com/runseb/egoscale/LICENSE | 201 +++++++++ .../src/github.com/runseb/egoscale/Makefile | 27 ++ .../src/github.com/runseb/egoscale/README.md | 12 + .../src/github.com/runseb/egoscale/exo.go | 96 +++++ .../runseb/egoscale/src/egoscale/async.go | 36 ++ .../runseb/egoscale/src/egoscale/error.go | 9 + .../runseb/egoscale/src/egoscale/groups.go | 105 +++++ .../runseb/egoscale/src/egoscale/init.go | 21 + .../runseb/egoscale/src/egoscale/keypair.go | 41 ++ .../runseb/egoscale/src/egoscale/request.go | 65 +++ .../runseb/egoscale/src/egoscale/topology.go | 170 ++++++++ .../runseb/egoscale/src/egoscale/types.go | 407 ++++++++++++++++++ .../runseb/egoscale/src/egoscale/vm.go | 162 +++++++ 15 files changed, 1358 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/.gitignore create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/LICENSE create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/Makefile create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/README.md create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/exo.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/async.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/error.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/groups.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/init.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/keypair.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/request.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/topology.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/types.go create mode 100644 Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/vm.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 1e0db122..5a70723f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -170,6 +170,10 @@ { "ImportPath": "github.com/kubernetes/helm/pkg/kubectl", "Rev": "4d7c681ba0aa43b45d122faa998c243422019be4" + }, + { + "ImportPath": "github.com/runseb/egoscale", + "Rev": "c57a032e2e6b8a7111724a0b3dab603cfac4c6cc" } ] } diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/.gitignore b/Godeps/_workspace/src/github.com/runseb/egoscale/.gitignore new file mode 100644 index 00000000..e8a2bb77 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/.gitignore @@ -0,0 +1,2 @@ +build/ +exo diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/LICENSE b/Godeps/_workspace/src/github.com/runseb/egoscale/LICENSE new file mode 100644 index 00000000..327ecb82 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 exoscale(tm) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/Makefile b/Godeps/_workspace/src/github.com/runseb/egoscale/Makefile new file mode 100644 index 00000000..e820a0b5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/Makefile @@ -0,0 +1,27 @@ +VERSION=0.3.0-snapshot +PREFIX?=/usr/local +GOPATH=$(PWD)/build:$(PWD) +PROGRAM=exo +GO=env GOPATH=$(GOPATH) go +RM?=rm -f +LN=ln -s +MAIN=exo.go +SRCS= src/egoscale/types.go \ + src/egoscale/error.go \ + src/egoscale/topology.go \ + src/egoscale/groups.go \ + src/egoscale/vm.go \ + src/egoscale/request.go \ + src/egoscale/async.go \ + src/egoscale/keypair.go \ + src/egoscale/init.go + +all: $(PROGRAM) + +$(PROGRAM): $(MAIN) $(SRCS) + $(GO) build egoscale + $(GO) build -o exo $(MAIN) + +clean: + $(RM) $(PROGRAM) + $(GO) clean diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/README.md b/Godeps/_workspace/src/github.com/runseb/egoscale/README.md new file mode 100644 index 00000000..66ebc888 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/README.md @@ -0,0 +1,12 @@ +egoscale: exoscale driver for golang +==================================== + +An API wrapper for the exoscale public cloud + +### License + +Licensed under the Apache License, Version 2.0 (the "License"); you +may not use this file except in compliance with the License. You may +obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 + diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/exo.go b/Godeps/_workspace/src/github.com/runseb/egoscale/exo.go new file mode 100644 index 00000000..0366da18 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/exo.go @@ -0,0 +1,96 @@ +package main + +import ( + "egoscale" + "os" + "fmt" + "time" +) + +func main() { + + endpoint := os.Getenv("EXOSCALE_ENDPOINT") + apiKey := os.Getenv("EXOSCALE_API_KEY") + apiSecret:= os.Getenv("EXOSCALE_API_SECRET") + client := egoscale.NewClient(endpoint, apiKey, apiSecret) + + topo, err := client.GetTopology() + if err != nil { + fmt.Printf("got error: %+v\n", err) + return + } + + rules := []egoscale.SecurityGroupRule{ + { + SecurityGroupId: "", + Cidr: "0.0.0.0/0", + Protocol: "TCP", + Port: 22, + }, + { + SecurityGroupId: "", + Cidr: "0.0.0.0/0", + Protocol: "TCP", + Port: 2376, + }, + { + SecurityGroupId: "", + Cidr: "0.0.0.0/0", + Protocol: "ICMP", + IcmpType: 8, + IcmpCode: 0, + }, + } + + sgid, present := topo.SecurityGroups["egoscale"] + if !present { + resp, err := client.CreateSecurityGroupWithRules("egoscale", rules, make([]egoscale.SecurityGroupRule,0,0)) + if err != nil { + fmt.Printf("got error: %+v\n", err) + return + } + sgid = resp.Id + } + + profile := egoscale.MachineProfile{ + Template: topo.Images["ubuntu-14.04"][10], + ServiceOffering: topo.Profiles["large"], + SecurityGroups: []string{ sgid,}, + Keypair: topo.Keypairs[0], + Userdata: "#cloud-config\nmanage_etc_hosts: true\nfqdn: deployed-by-egoscale\n", + Zone: topo.Zones["ch-gva-2"], + Name: "deployed-by-egoscale", + } + + jobid, err := client.CreateVirtualMachine(profile) + + if err != nil { + fmt.Printf("got error: %+v\n", err) + return + } + + var resp *egoscale.QueryAsyncJobResultResponse + + for i := 0; i <= 10; i++ { + resp, err = client.PollAsyncJob(jobid) + if err != nil { + fmt.Printf("got error: %+v\n", err) + return + } + + if (resp.Jobstatus == 1) { + break + } + time.Sleep(5 * time.Second) + } + + vm, err := client.AsyncToVirtualMachine(*resp) + + if err != nil { + fmt.Printf("got error: %+v\n", err) + } + + fmt.Printf("new machine up and running at: %s\n", vm.Nic[0].Ipaddress) + + +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/async.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/async.go new file mode 100644 index 00000000..84401e7a --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/async.go @@ -0,0 +1,36 @@ +package egoscale + +import ( + "encoding/json" + "net/url" +) + +func (exo *Client) PollAsyncJob(jobid string) (*QueryAsyncJobResultResponse, error) { + params := url.Values{} + + params.Set("jobid", jobid) + + resp, err := exo.Request("queryAsyncJobResult", params) + + if err != nil { + return nil, err + } + + var r QueryAsyncJobResultResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return &r, nil +} + +func (exo *Client) AsyncToVirtualMachine(resp QueryAsyncJobResultResponse) (*DeployVirtualMachineResponse, error) { + var r DeployVirtualMachineWrappedResponse + + if err := json.Unmarshal(resp.Jobresult, &r); err != nil { + return nil, err + } + + return &r.Wrapped, nil +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/error.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/error.go new file mode 100644 index 00000000..3e7d2c77 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/error.go @@ -0,0 +1,9 @@ +package egoscale + +import ( + "fmt" +) + +func (e *Error) Error() error { + return fmt.Errorf("exoscale API error %d (internal code: %d): %s", e.ErrorCode, e.CSErrorCode, e.ErrorText) +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/groups.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/groups.go new file mode 100644 index 00000000..5a2f6f9d --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/groups.go @@ -0,0 +1,105 @@ +package egoscale + +import ( + "encoding/json" + "fmt" + "net/url" +) + +func (exo *Client) CreateEgressRule(rule SecurityGroupRule) (*AuthorizeSecurityGroupEgressResponse, error) { + + params := url.Values{} + params.Set("securitygroupid", rule.SecurityGroupId) + params.Set("cidrlist", rule.Cidr) + params.Set("protocol", rule.Protocol) + + if rule.Protocol == "ICMP" { + params.Set("icmpcode", fmt.Sprintf("%d", rule.IcmpCode)) + params.Set("icmptype", fmt.Sprintf("%d", rule.IcmpType)) + } else if rule.Protocol == "TCP" || rule.Protocol == "UDP" { + params.Set("startport", fmt.Sprintf("%d", rule.Port)) + params.Set("endport", fmt.Sprintf("%d", rule.Port)) + } else { + return nil, fmt.Errorf("Invalid Egress rule Protocol: %s", rule.Protocol) + } + + resp, err := exo.Request("authorizeSecurityGroupEgress", params) + if err != nil { + return nil, err + } + + var r AuthorizeSecurityGroupEgressResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return &r, nil +} + +func (exo *Client) CreateIngressRule(rule SecurityGroupRule) (*AuthorizeSecurityGroupIngressResponse, error) { + + params := url.Values{} + params.Set("securitygroupid", rule.SecurityGroupId) + params.Set("cidrlist", rule.Cidr) + params.Set("protocol", rule.Protocol) + + if rule.Protocol == "ICMP" { + params.Set("icmpcode", fmt.Sprintf("%d", rule.IcmpCode)) + params.Set("icmptype", fmt.Sprintf("%d", rule.IcmpType)) + } else if rule.Protocol == "TCP" || rule.Protocol == "UDP" { + params.Set("startport", fmt.Sprintf("%d", rule.Port)) + params.Set("endport", fmt.Sprintf("%d", rule.Port)) + } else { + return nil, fmt.Errorf("Invalid Egress rule Protocol: %s", rule.Protocol) + } + + resp, err := exo.Request("authorizeSecurityGroupIngress", params) + + if err != nil { + return nil, err + } + + var r AuthorizeSecurityGroupIngressResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return &r, nil +} + +func (exo *Client) CreateSecurityGroupWithRules(name string, ingress []SecurityGroupRule, egress []SecurityGroupRule) (*CreateSecurityGroupResponse, error) { + + params := url.Values{} + params.Set("name", name) + + resp, err := exo.Request("createSecurityGroup", params) + + var r CreateSecurityGroupResponseWrapper + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + sgid := r.Wrapped.Id + + for _, erule := range egress { + erule.SecurityGroupId = sgid + _, err = exo.CreateEgressRule(erule) + if err != nil { + return nil, err + } + } + + for _, inrule := range ingress { + inrule.SecurityGroupId = sgid + _, err = exo.CreateIngressRule(inrule) + if err != nil { + return nil, err + } + } + + return &r.Wrapped, nil +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/init.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/init.go new file mode 100644 index 00000000..77b46cf9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/init.go @@ -0,0 +1,21 @@ +package egoscale + +import ( + "crypto/tls" + "net/http" +) + +func NewClient(endpoint string, apiKey string, apiSecret string) *Client { + cs := &Client{ + client: &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, + }, + }, + endpoint: endpoint, + apiKey: apiKey, + apiSecret: apiSecret, + } + return cs +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/keypair.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/keypair.go new file mode 100644 index 00000000..f955cd0c --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/keypair.go @@ -0,0 +1,41 @@ +package egoscale + +import ( + "encoding/json" + "net/url" +) + +func (exo *Client) CreateKeypair(name string) (*CreateSSHKeyPairResponse, error) { + params := url.Values{} + params.Set("name", name) + + resp, err := exo.Request("createSSHKeyPair", params) + if err != nil { + return nil, err + } + + var r CreateSSHKeyPairWrappedResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return &r.Wrapped, nil +} + +func (exo *Client) DeleteKeypair(name string) (*StandardResponse, error) { + params := url.Values{} + params.Set("name", name) + + resp, err := exo.Request("deleteSSHKeyPair", params) + if err != nil { + return nil, err + } + + var r StandardResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return &r, nil + +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/request.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/request.go new file mode 100644 index 00000000..9fa235dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/request.go @@ -0,0 +1,65 @@ +package egoscale + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "strings" +) + +func rawValue(b json.RawMessage) (json.RawMessage, error) { + var m map[string]json.RawMessage + + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + for _, v := range m { + return v, nil + } + return nil, fmt.Errorf("Unable to extract raw value from:\n\n%s\n\n", string(b)) +} + +func (exo *Client) Request(command string, params url.Values) (json.RawMessage, error) { + + mac := hmac.New(sha1.New, []byte(exo.apiSecret)) + + params.Set("apikey", exo.apiKey) + params.Set("command", command) + params.Set("response", "json") + + s := strings.Replace(strings.ToLower(params.Encode()), "+", "%20", -1) + mac.Write([]byte(s)) + signature := url.QueryEscape(base64.StdEncoding.EncodeToString(mac.Sum(nil))) + + s = params.Encode() + url := exo.endpoint + "?" + s + "&signature=" + signature + + resp, err := exo.client.Get(url) + if err != nil { + return nil, err + } + + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + + b, err = rawValue(b) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + var e Error + if err := json.Unmarshal(b, &e); err != nil { + return nil, err + } + return nil, e.Error() + } + return b, nil +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/topology.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/topology.go new file mode 100644 index 00000000..908c23c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/topology.go @@ -0,0 +1,170 @@ +package egoscale + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + "strings" +) + +func (exo *Client) GetSecurityGroups() (map[string]string, error) { + var sgs map[string]string + params := url.Values{} + resp, err := exo.Request("listSecurityGroups", params) + + if err != nil { + return nil, err + } + + var r ListSecurityGroupsResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + sgs = make(map[string]string) + for _, sg := range r.SecurityGroups { + sgs[sg.Name] = sg.Id + } + return sgs, nil +} + +func (exo *Client) GetZones() (map[string]string, error) { + var zones map[string]string + params := url.Values{} + resp, err := exo.Request("listZones", params) + + if err != nil { + return nil, err + } + + var r ListZonesResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + zones = make(map[string]string) + for _, zone := range r.Zones { + zones[zone.Name] = zone.Id + } + return zones, nil +} + +func (exo *Client) GetProfiles() (map[string]string, error) { + + var profiles map[string]string + params := url.Values{} + resp, err := exo.Request("listServiceOfferings", params) + + if err != nil { + return nil, err + } + + var r ListServiceOfferingsResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + profiles = make(map[string]string) + for _, offering := range r.ServiceOfferings { + profiles[strings.ToLower(offering.Name)] = offering.Id + } + + return profiles, nil +} + +func (exo *Client) GetKeypairs() ([]string, error) { + + var keypairs []string + params := url.Values{} + + resp, err := exo.Request("listSSHKeyPairs", params) + + if err != nil { + return nil, err + } + + var r ListSSHKeyPairsResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + keypairs = make([]string, r.Count, r.Count) + for i, keypair := range r.SSHKeyPairs { + keypairs[i] = keypair.Name + } + return keypairs, nil +} + +func (exo *Client) GetImages() (map[string]map[int]string, error) { + var images map[string]map[int]string + images = make(map[string]map[int]string) + + params := url.Values{} + params.Set("templatefilter", "executable") + + resp, err := exo.Request("listTemplates", params) + + if err != nil { + return nil, err + } + + var r ListTemplatesResponse + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + re := regexp.MustCompile(`^Linux (?P.+?) (?P[0-9.]+).*$`) + for _, template := range r.Templates { + size := int(template.Size / (1024 * 1024 * 1024)) + submatch := re.FindStringSubmatch(template.Name) + if len(submatch) > 0 { + name := strings.Replace(strings.ToLower(submatch[1]), " ", "-", -1) + version := submatch[2] + image := fmt.Sprintf("%s-%s", name, version) + + _, present := images[image] + if !present { + images[image] = make(map[int]string) + } + images[image][size] = template.Id + + images[fmt.Sprintf("%s-%s", name, version)][size] = template.Id + } + } + return images, nil +} + +func (exo *Client) GetTopology() (*Topology, error) { + + zones, err := exo.GetZones() + if err != nil { + return nil, err + } + images, err := exo.GetImages() + if err != nil { + return nil, err + } + groups, err := exo.GetSecurityGroups() + if err != nil { + return nil, err + } + keypairs, err := exo.GetKeypairs() + if err != nil { + return nil, err + } + profiles, err := exo.GetProfiles() + if err != nil { + return nil, err + } + + topo := &Topology{ + Zones: zones, + Profiles: profiles, + Images: images, + Keypairs: keypairs, + SecurityGroups: groups, + } + + return topo, nil +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/types.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/types.go new file mode 100644 index 00000000..45dba670 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/types.go @@ -0,0 +1,407 @@ +package egoscale + +import ( + "encoding/json" + "net/http" +) + +type Client struct { + client *http.Client + endpoint string + apiKey string + apiSecret string +} + +type Error struct { + ErrorCode int `json:"errorcode"` + CSErrorCode int `json:"cserrorcode"` + ErrorText string `json:"errortext"` +} + +type StandardResponse struct { + Success string `json:"success"` + DisplayText string `json:"displaytext"` +} + +type Topology struct { + Zones map[string]string + Images map[string]map[int]string + Profiles map[string]string + Keypairs []string + SecurityGroups map[string]string +} + +type SecurityGroupRule struct { + Cidr string + IcmpType int + IcmpCode int + Port int + Protocol string + SecurityGroupId string +} + +type MachineProfile struct { + Name string + SecurityGroups []string + Keypair string + Userdata string + ServiceOffering string + Template string + Zone string +} + +type ListZonesResponse struct { + Count int `json:"count"` + Zones []*Zone `json:"zone"` +} + +type Zone struct { + Allocationstate string `json:"allocationstate,omitempty"` + Description string `json:"description,omitempty"` + Displaytext string `json:"displaytext,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Domainname string `json:"domainname,omitempty"` + Id string `json:"id,omitempty"` + Internaldns1 string `json:"internaldns1,omitempty"` + Internaldns2 string `json:"internaldns2,omitempty"` + Ip6dns1 string `json:"ip6dns1,omitempty"` + Ip6dns2 string `json:"ip6dns2,omitempty"` + Localstorageenabled bool `json:"localstorageenabled,omitempty"` + Name string `json:"name,omitempty"` + Networktype string `json:"networktype,omitempty"` + Resourcedetails map[string]string `json:"resourcedetails,omitempty"` + Securitygroupsenabled bool `json:"securitygroupsenabled,omitempty"` + Vlan string `json:"vlan,omitempty"` + Zonetoken string `json:"zonetoken,omitempty"` +} + +type ListServiceOfferingsResponse struct { + Count int `json:"count"` + ServiceOfferings []*ServiceOffering `json:"serviceoffering"` +} + +type ServiceOffering struct { + Cpunumber int `json:"cpunumber,omitempty"` + Cpuspeed int `json:"cpuspeed,omitempty"` + Displaytext string `json:"displaytext,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Hosttags string `json:"hosttags,omitempty"` + Id string `json:"id,omitempty"` + Iscustomized bool `json:"iscustomized,omitempty"` + Issystem bool `json:"issystem,omitempty"` + Isvolatile bool `json:"isvolatile,omitempty"` + Memory int `json:"memory,omitempty"` + Name string `json:"name,omitempty"` + Networkrate int `json:"networkrate,omitempty"` + Serviceofferingdetails map[string]string `json:"serviceofferingdetails,omitempty"` +} + +type ListTemplatesResponse struct { + Count int `json:"count"` + Templates []*Template `json:"template"` +} + +type Template struct { + Account string `json:"account,omitempty"` + Accountid string `json:"accountid,omitempty"` + Bootable bool `json:"bootable,omitempty"` + Checksum string `json:"checksum,omitempty"` + Created string `json:"created,omitempty"` + CrossZones bool `json:"crossZones,omitempty"` + Details map[string]string `json:"details,omitempty"` + Displaytext string `json:"displaytext,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Format string `json:"format,omitempty"` + Hostid string `json:"hostid,omitempty"` + Hostname string `json:"hostname,omitempty"` + Hypervisor string `json:"hypervisor,omitempty"` + Id string `json:"id,omitempty"` + Isdynamicallyscalable bool `json:"isdynamicallyscalable,omitempty"` + Isextractable bool `json:"isextractable,omitempty"` + Isfeatured bool `json:"isfeatured,omitempty"` + Ispublic bool `json:"ispublic,omitempty"` + Isready bool `json:"isready,omitempty"` + Name string `json:"name,omitempty"` + Ostypeid string `json:"ostypeid,omitempty"` + Ostypename string `json:"ostypename,omitempty"` + Passwordenabled bool `json:"passwordenabled,omitempty"` + Project string `json:"project,omitempty"` + Projectid string `json:"projectid,omitempty"` + Removed string `json:"removed,omitempty"` + Size int64 `json:"size,omitempty"` + Sourcetemplateid string `json:"sourcetemplateid,omitempty"` + Sshkeyenabled bool `json:"sshkeyenabled,omitempty"` + Status string `json:"status,omitempty"` + Zoneid string `json:"zoneid,omitempty"` + Zonename string `json:"zonename,omitempty"` +} + +type ListSSHKeyPairsResponse struct { + Count int `json:"count"` + SSHKeyPairs []*SSHKeyPair `json:"sshkeypair"` +} + +type SSHKeyPair struct { + Fingerprint string `json:"fingerprint,omitempty"` + Name string `json:"name,omitempty"` +} + +type ListSecurityGroupsResponse struct { + Count int `json:"count"` + SecurityGroups []*SecurityGroup `json:"securitygroup"` +} + +type SecurityGroup struct { + Account string `json:"account,omitempty"` + Description string `json:"description,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Project string `json:"project,omitempty"` + Projectid string `json:"projectid,omitempty"` +} + +type CreateSecurityGroupResponseWrapper struct { + Wrapped CreateSecurityGroupResponse `json:"securitygroup"` +} +type CreateSecurityGroupResponse struct { + Account string `json:"account,omitempty"` + Description string `json:"description,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Project string `json:"project,omitempty"` + Projectid string `json:"projectid,omitempty"` +} + +type AuthorizeSecurityGroupIngressResponse struct { + JobID string `json:"jobid,omitempty"` + Account string `json:"account,omitempty"` + Cidr string `json:"cidr,omitempty"` + Endport int `json:"endport,omitempty"` + Icmpcode int `json:"icmpcode,omitempty"` + Icmptype int `json:"icmptype,omitempty"` + Protocol string `json:"protocol,omitempty"` + Ruleid string `json:"ruleid,omitempty"` + Securitygroupname string `json:"securitygroupname,omitempty"` + Startport int `json:"startport,omitempty"` +} + +type AuthorizeSecurityGroupEgressResponse struct { + JobID string `json:"jobid,omitempty"` + Account string `json:"account,omitempty"` + Cidr string `json:"cidr,omitempty"` + Endport int `json:"endport,omitempty"` + Icmpcode int `json:"icmpcode,omitempty"` + Icmptype int `json:"icmptype,omitempty"` + Protocol string `json:"protocol,omitempty"` + Ruleid string `json:"ruleid,omitempty"` + Securitygroupname string `json:"securitygroupname,omitempty"` + Startport int `json:"startport,omitempty"` +} + +type DeployVirtualMachineWrappedResponse struct { + Wrapped DeployVirtualMachineResponse `json:"virtualmachine"` +} + +type DeployVirtualMachineResponse struct { + JobID string `json:"jobid,omitempty"` + Account string `json:"account,omitempty"` + Affinitygroup []struct { + Account string `json:"account,omitempty"` + Description string `json:"description,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + VirtualmachineIds []string `json:"virtualmachineIds,omitempty"` + } `json:"affinitygroup,omitempty"` + Cpunumber int `json:"cpunumber,omitempty"` + Cpuspeed int `json:"cpuspeed,omitempty"` + Cpuused string `json:"cpuused,omitempty"` + Created string `json:"created,omitempty"` + Details map[string]string `json:"details,omitempty"` + Diskioread int64 `json:"diskioread,omitempty"` + Diskiowrite int64 `json:"diskiowrite,omitempty"` + Diskkbsread int64 `json:"diskkbsread,omitempty"` + Diskkbswrite int64 `json:"diskkbswrite,omitempty"` + Displayname string `json:"displayname,omitempty"` + Displayvm bool `json:"displayvm,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Forvirtualnetwork bool `json:"forvirtualnetwork,omitempty"` + Group string `json:"group,omitempty"` + Groupid string `json:"groupid,omitempty"` + Guestosid string `json:"guestosid,omitempty"` + Haenable bool `json:"haenable,omitempty"` + Hostid string `json:"hostid,omitempty"` + Hostname string `json:"hostname,omitempty"` + Hypervisor string `json:"hypervisor,omitempty"` + Id string `json:"id,omitempty"` + Instancename string `json:"instancename,omitempty"` + Isdynamicallyscalable bool `json:"isdynamicallyscalable,omitempty"` + Isodisplaytext string `json:"isodisplaytext,omitempty"` + Isoid string `json:"isoid,omitempty"` + Isoname string `json:"isoname,omitempty"` + Keypair string `json:"keypair,omitempty"` + Memory int `json:"memory,omitempty"` + Name string `json:"name,omitempty"` + Networkkbsread int64 `json:"networkkbsread,omitempty"` + Networkkbswrite int64 `json:"networkkbswrite,omitempty"` + Nic []struct { + Broadcasturi string `json:"broadcasturi,omitempty"` + Gateway string `json:"gateway,omitempty"` + Id string `json:"id,omitempty"` + Ipaddress string `json:"ipaddress,omitempty"` + Isdefault bool `json:"isdefault,omitempty"` + Isolationuri string `json:"isolationuri,omitempty"` + Macaddress string `json:"macaddress,omitempty"` + Netmask string `json:"netmask,omitempty"` + Networkid string `json:"networkid,omitempty"` + Networkname string `json:"networkname,omitempty"` + Secondaryip []string `json:"secondaryip,omitempty"` + Traffictype string `json:"traffictype,omitempty"` + Type string `json:"type,omitempty"` + } `json:"nic,omitempty"` + Password string `json:"password,omitempty"` + Passwordenabled bool `json:"passwordenabled,omitempty"` + Project string `json:"project,omitempty"` + Projectid string `json:"projectid,omitempty"` + Publicip string `json:"publicip,omitempty"` + Publicipid string `json:"publicipid,omitempty"` + Rootdeviceid int64 `json:"rootdeviceid,omitempty"` + Rootdevicetype string `json:"rootdevicetype,omitempty"` + Serviceofferingid string `json:"serviceofferingid,omitempty"` + Serviceofferingname string `json:"serviceofferingname,omitempty"` + Servicestate string `json:"servicestate,omitempty"` + State string `json:"state,omitempty"` + Templatedisplaytext string `json:"templatedisplaytext,omitempty"` + Templateid string `json:"templateid,omitempty"` + Templatename string `json:"templatename,omitempty"` + Zoneid string `json:"zoneid,omitempty"` + Zonename string `json:"zonename,omitempty"` +} + +type QueryAsyncJobResultResponse struct { + Accountid string `json:"accountid,omitempty"` + Cmd string `json:"cmd,omitempty"` + Created string `json:"created,omitempty"` + Jobinstanceid string `json:"jobinstanceid,omitempty"` + Jobinstancetype string `json:"jobinstancetype,omitempty"` + Jobprocstatus int `json:"jobprocstatus,omitempty"` + Jobresult json.RawMessage `json:"jobresult,omitempty"` + Jobresultcode int `json:"jobresultcode,omitempty"` + Jobresulttype string `json:"jobresulttype,omitempty"` + Jobstatus int `json:"jobstatus,omitempty"` + Userid string `json:"userid,omitempty"` +} + +type ListVirtualMachinesResponse struct { + Count int `json:"count"` + VirtualMachines []*VirtualMachine `json:"virtualmachine"` +} + +type VirtualMachine struct { + Account string `json:"account,omitempty"` + Cpunumber int `json:"cpunumber,omitempty"` + Cpuspeed int `json:"cpuspeed,omitempty"` + Cpuused string `json:"cpuused,omitempty"` + Created string `json:"created,omitempty"` + Details map[string]string `json:"details,omitempty"` + Diskioread int64 `json:"diskioread,omitempty"` + Diskiowrite int64 `json:"diskiowrite,omitempty"` + Diskkbsread int64 `json:"diskkbsread,omitempty"` + Diskkbswrite int64 `json:"diskkbswrite,omitempty"` + Displayname string `json:"displayname,omitempty"` + Displayvm bool `json:"displayvm,omitempty"` + Domain string `json:"domain,omitempty"` + Domainid string `json:"domainid,omitempty"` + Forvirtualnetwork bool `json:"forvirtualnetwork,omitempty"` + Group string `json:"group,omitempty"` + Groupid string `json:"groupid,omitempty"` + Guestosid string `json:"guestosid,omitempty"` + Haenable bool `json:"haenable,omitempty"` + Hostid string `json:"hostid,omitempty"` + Hostname string `json:"hostname,omitempty"` + Hypervisor string `json:"hypervisor,omitempty"` + Id string `json:"id,omitempty"` + Instancename string `json:"instancename,omitempty"` + Isdynamicallyscalable bool `json:"isdynamicallyscalable,omitempty"` + Isodisplaytext string `json:"isodisplaytext,omitempty"` + Isoid string `json:"isoid,omitempty"` + Isoname string `json:"isoname,omitempty"` + Keypair string `json:"keypair,omitempty"` + Memory int `json:"memory,omitempty"` + Name string `json:"name,omitempty"` + Networkkbsread int64 `json:"networkkbsread,omitempty"` + Networkkbswrite int64 `json:"networkkbswrite,omitempty"` + Nic []struct { + Broadcasturi string `json:"broadcasturi,omitempty"` + Gateway string `json:"gateway,omitempty"` + Id string `json:"id,omitempty"` + Ip6address string `json:"ip6address,omitempty"` + Ip6cidr string `json:"ip6cidr,omitempty"` + Ip6gateway string `json:"ip6gateway,omitempty"` + Ipaddress string `json:"ipaddress,omitempty"` + Isdefault bool `json:"isdefault,omitempty"` + Isolationuri string `json:"isolationuri,omitempty"` + Macaddress string `json:"macaddress,omitempty"` + Netmask string `json:"netmask,omitempty"` + Networkid string `json:"networkid,omitempty"` + Networkname string `json:"networkname,omitempty"` + Secondaryip []string `json:"secondaryip,omitempty"` + Traffictype string `json:"traffictype,omitempty"` + Type string `json:"type,omitempty"` + } `json:"nic,omitempty"` + Password string `json:"password,omitempty"` + Passwordenabled bool `json:"passwordenabled,omitempty"` + Project string `json:"project,omitempty"` + Projectid string `json:"projectid,omitempty"` + Publicip string `json:"publicip,omitempty"` + Publicipid string `json:"publicipid,omitempty"` + Rootdeviceid int64 `json:"rootdeviceid,omitempty"` + Rootdevicetype string `json:"rootdevicetype,omitempty"` + Serviceofferingid string `json:"serviceofferingid,omitempty"` + Serviceofferingname string `json:"serviceofferingname,omitempty"` + Servicestate string `json:"servicestate,omitempty"` + State string `json:"state,omitempty"` + Templatedisplaytext string `json:"templatedisplaytext,omitempty"` + Templateid string `json:"templateid,omitempty"` + Templatename string `json:"templatename,omitempty"` + Zoneid string `json:"zoneid,omitempty"` + Zonename string `json:"zonename,omitempty"` +} + +type StartVirtualMachineResponse struct { + JobID string `json:"jobid,omitempty"` +} + +type StopVirtualMachineResponse struct { + JobID string `json:"jobid,omitempty"` +} + +type DestroyVirtualMachineResponse struct { + JobID string `json:"jobid,omitempty"` +} + +type RebootVirtualMachineResponse struct { + JobID string `json:"jobid,omitempty"` +} + +type CreateSSHKeyPairWrappedResponse struct { + Wrapped CreateSSHKeyPairResponse `json:"keypair,omitempty"` +} + +type CreateSSHKeyPairResponse struct { + Privatekey string `json:"privatekey,omitempty"` +} + +type DeleteSSHKeyPairResponse struct { + Privatekey string `json:"privatekey,omitempty"` +} diff --git a/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/vm.go b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/vm.go new file mode 100644 index 00000000..8c8ea985 --- /dev/null +++ b/Godeps/_workspace/src/github.com/runseb/egoscale/src/egoscale/vm.go @@ -0,0 +1,162 @@ +package egoscale + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "strings" +) + +func (exo *Client) CreateVirtualMachine(p MachineProfile) (string, error) { + + params := url.Values{} + params.Set("serviceofferingid", p.ServiceOffering) + params.Set("templateid", p.Template) + params.Set("zoneid", p.Zone) + + params.Set("displayname", p.Name) + if len(p.Userdata) > 0 { + params.Set("userdata", base64.StdEncoding.EncodeToString([]byte(p.Userdata))) + } + if len(p.Keypair) > 0 { + params.Set("keypair", p.Keypair) + } + + params.Set("securitygroupids", strings.Join(p.SecurityGroups, ",")) + + resp, err := exo.Request("deployVirtualMachine", params) + + if err != nil { + return "", err + } + + var r DeployVirtualMachineResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return "", err + } + + return r.JobID, nil +} + +func (exo *Client) StartVirtualMachine(id string) (string, error) { + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("startVirtualMachine", params) + + if err != nil { + return "", err + } + + var r StartVirtualMachineResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return "", err + } + + return r.JobID, nil +} + +func (exo *Client) StopVirtualMachine(id string) (string, error) { + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("stopVirtualMachine", params) + + if err != nil { + return "", err + } + + var r StopVirtualMachineResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return "", err + } + + return r.JobID, nil +} + +func (exo *Client) RebootVirtualMachine(id string) (string, error) { + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("rebootVirtualMachine", params) + + if err != nil { + return "", err + } + + var r RebootVirtualMachineResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return "", err + } + + return r.JobID, nil +} + +func (exo *Client) DestroyVirtualMachine(id string) (string, error) { + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("destroyVirtualMachine", params) + + if err != nil { + return "", err + } + + var r DestroyVirtualMachineResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return "", err + } + + return r.JobID, nil +} + +func (exo *Client) GetVirtualMachine(id string) (*VirtualMachine, error) { + + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("listVirtualMachines", params) + + if err != nil { + return nil, err + } + + var r ListVirtualMachinesResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + if len(r.VirtualMachines) == 1 { + machine := r.VirtualMachines[0] + return machine, nil + } else { + return nil, fmt.Errorf("cannot retrieve virtualmachine with id %s", id) + } +} + +func (exo *Client) ListVirtualMachines(id string) ([]*VirtualMachine, error) { + + params := url.Values{} + params.Set("id", id) + + resp, err := exo.Request("listVirtualMachines", params) + + if err != nil { + return nil, err + } + + var r ListVirtualMachinesResponse + + if err := json.Unmarshal(resp, &r); err != nil { + return nil, err + } + + return r.VirtualMachines, nil +}