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  }