github.com/phobos182/packer@v0.2.3-0.20130819023704-c84d2aeffc68/builder/digitalocean/api.go (about) 1 // All of the methods used to communicate with the digital_ocean API 2 // are here. Their API is on a path to V2, so just plain JSON is used 3 // in place of a proper client library for now. 4 5 package digitalocean 6 7 import ( 8 "encoding/json" 9 "errors" 10 "fmt" 11 "github.com/mitchellh/mapstructure" 12 "io/ioutil" 13 "log" 14 "net/http" 15 "net/url" 16 "strings" 17 ) 18 19 const DIGITALOCEAN_API_URL = "https://api.digitalocean.com" 20 21 type Image struct { 22 Id uint 23 Name string 24 Distribution string 25 } 26 27 type ImagesResp struct { 28 Images []Image 29 } 30 31 type DigitalOceanClient struct { 32 // The http client for communicating 33 client *http.Client 34 35 // The base URL of the API 36 BaseURL string 37 38 // Credentials 39 ClientID string 40 APIKey string 41 } 42 43 // Creates a new client for communicating with DO 44 func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient { 45 c := &DigitalOceanClient{ 46 client: &http.Client{ 47 Transport: &http.Transport{ 48 Proxy: http.ProxyFromEnvironment, 49 }, 50 }, 51 BaseURL: DIGITALOCEAN_API_URL, 52 ClientID: client, 53 APIKey: key, 54 } 55 return c 56 } 57 58 // Creates an SSH Key and returns it's id 59 func (d DigitalOceanClient) CreateKey(name string, pub string) (uint, error) { 60 params := url.Values{} 61 params.Set("name", name) 62 params.Set("ssh_pub_key", pub) 63 64 body, err := NewRequest(d, "ssh_keys/new", params) 65 if err != nil { 66 return 0, err 67 } 68 69 // Read the SSH key's ID we just created 70 key := body["ssh_key"].(map[string]interface{}) 71 keyId := key["id"].(float64) 72 return uint(keyId), nil 73 } 74 75 // Destroys an SSH key 76 func (d DigitalOceanClient) DestroyKey(id uint) error { 77 path := fmt.Sprintf("ssh_keys/%v/destroy", id) 78 _, err := NewRequest(d, path, url.Values{}) 79 return err 80 } 81 82 // Creates a droplet and returns it's id 83 func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint) (uint, error) { 84 params := url.Values{} 85 params.Set("name", name) 86 params.Set("size_id", fmt.Sprintf("%v", size)) 87 params.Set("image_id", fmt.Sprintf("%v", image)) 88 params.Set("region_id", fmt.Sprintf("%v", region)) 89 params.Set("ssh_key_ids", fmt.Sprintf("%v", keyId)) 90 91 body, err := NewRequest(d, "droplets/new", params) 92 if err != nil { 93 return 0, err 94 } 95 96 // Read the Droplets ID 97 droplet := body["droplet"].(map[string]interface{}) 98 dropletId := droplet["id"].(float64) 99 return uint(dropletId), err 100 } 101 102 // Destroys a droplet 103 func (d DigitalOceanClient) DestroyDroplet(id uint) error { 104 path := fmt.Sprintf("droplets/%v/destroy", id) 105 _, err := NewRequest(d, path, url.Values{}) 106 return err 107 } 108 109 // Powers off a droplet 110 func (d DigitalOceanClient) PowerOffDroplet(id uint) error { 111 path := fmt.Sprintf("droplets/%v/power_off", id) 112 113 _, err := NewRequest(d, path, url.Values{}) 114 115 return err 116 } 117 118 // Creates a snaphot of a droplet by it's ID 119 func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error { 120 path := fmt.Sprintf("droplets/%v/snapshot", id) 121 122 params := url.Values{} 123 params.Set("name", name) 124 125 _, err := NewRequest(d, path, params) 126 127 return err 128 } 129 130 // Returns all available images. 131 func (d DigitalOceanClient) Images() ([]Image, error) { 132 resp, err := NewRequest(d, "images", url.Values{}) 133 if err != nil { 134 return nil, err 135 } 136 137 var result ImagesResp 138 if err := mapstructure.Decode(resp, &result); err != nil { 139 return nil, err 140 } 141 142 return result.Images, nil 143 } 144 145 // Destroys an image by its ID. 146 func (d DigitalOceanClient) DestroyImage(id uint) error { 147 path := fmt.Sprintf("images/%d/destroy", id) 148 _, err := NewRequest(d, path, url.Values{}) 149 return err 150 } 151 152 // Returns DO's string representation of status "off" "new" "active" etc. 153 func (d DigitalOceanClient) DropletStatus(id uint) (string, string, error) { 154 path := fmt.Sprintf("droplets/%v", id) 155 156 body, err := NewRequest(d, path, url.Values{}) 157 if err != nil { 158 return "", "", err 159 } 160 161 var ip string 162 163 // Read the droplet's "status" 164 droplet := body["droplet"].(map[string]interface{}) 165 status := droplet["status"].(string) 166 167 if droplet["ip_address"] != nil { 168 ip = droplet["ip_address"].(string) 169 } 170 171 return ip, status, err 172 } 173 174 // Sends an api request and returns a generic map[string]interface of 175 // the response. 176 func NewRequest(d DigitalOceanClient, path string, params url.Values) (map[string]interface{}, error) { 177 client := d.client 178 179 // Add the authentication parameters 180 params.Set("client_id", d.ClientID) 181 params.Set("api_key", d.APIKey) 182 183 url := fmt.Sprintf("%s/%s?%s", DIGITALOCEAN_API_URL, path, params.Encode()) 184 185 var decodedResponse map[string]interface{} 186 187 // Do some basic scrubbing so sensitive information doesn't appear in logs 188 scrubbedUrl := strings.Replace(url, d.ClientID, "CLIENT_ID", -1) 189 scrubbedUrl = strings.Replace(scrubbedUrl, d.APIKey, "API_KEY", -1) 190 log.Printf("sending new request to digitalocean: %s", scrubbedUrl) 191 192 resp, err := client.Get(url) 193 if err != nil { 194 return decodedResponse, err 195 } 196 197 body, err := ioutil.ReadAll(resp.Body) 198 199 resp.Body.Close() 200 if err != nil { 201 return decodedResponse, err 202 } 203 204 log.Printf("response from digitalocean: %s", body) 205 206 err = json.Unmarshal(body, &decodedResponse) 207 208 // Check for bad JSON 209 if err != nil { 210 err = errors.New(fmt.Sprintf("Failed to decode JSON response (HTTP %v) from DigitalOcean: %s", 211 resp.StatusCode, body)) 212 return decodedResponse, err 213 } 214 215 // Check for errors sent by digitalocean 216 status := decodedResponse["status"] 217 if status != "OK" { 218 // Get the actual error message if there is one 219 if status == "ERROR" { 220 status = decodedResponse["error_message"] 221 } 222 err = errors.New(fmt.Sprintf("Received bad response (HTTP %v) from DigitalOcean: %s", resp.StatusCode, status)) 223 return decodedResponse, err 224 } 225 226 return decodedResponse, nil 227 }