Creating a DigitalOcean Droplet with Golang

Create Droplets with ease on DigitalOcean infrastructure using Golang and DO's REST API!

Creating a DigitalOcean Droplet with Golang

In the last blog on using Golang to interact with the DigitalOcean API we covered how to use Golang to list the droplets, DigitalOcean's term for Virtual Private Servers (VPS's), you have initialized on your account. That's fun if you have already created a few droplets, but it's much more fun if you can create and destroy droplets as well. Luckily, this is super easy and only requires the usage of a couple of new endpoints!

Creating a Droplet

func grabDrops(DOAPIKEY string) (Droplets, error) {

	dropleturl, err := url.JoinPath(BASEURL, "droplets")
	if err != nil {
		fmt.Errorf("There was an error trying to create the droplet URL: %s\n", err.Error())
		return Droplets{}, err
	}
	fmt.Println(dropleturl)
	req, err := makeReq("GET", DOAPIKEY, dropleturl, nil)
	client := http.Client{}
	res, err := client.Do(req)
	if err != nil {
		fmt.Errorf("There was an error doing the request: %s", err.Error())
		return Droplets{}, err
	}
	resbody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Errorf("There was an error reading the response body: %s", err.Error())
		return Droplets{}, err
	}
	//fmt.Println(string(resbody))
	drops := Droplets{}
	err = json.Unmarshal(resbody, &drops)
	if err != nil {
		fmt.Errorf("There was an error unmarshaling the struct: %s", err.Error())
		return Droplets{}, err
	}
	// for _, drop := range drops.Drops {
	// 	fmt.Printf("Droplet ID %d\n\tName: %s\n\tCreated At: %s\n", drop.Id, drop.Name, drop.CreatedAt)
	// 	fmt.Printf("\tImage info:\n\t\tImage ID: %d\n\t\tImage name: %s\n\n", drop.Image.Id, drop.Image.Name)
	// }
	return drops, nil

}

Let's break our old code out into its own function to list all of our droplets. We'll also tweak it to return this list of droplets instead of just printing it out. To create a droplet, we're going to need to set up a ew things. Specifically, DigitalOcean has some input parameters that it wants us to pass in to initialize our droplets. We need to create a blob with a name, image and size variable set at the bare minimum. We will pass the name into the function as an input parameter and hardcode the image and size variable. You can find the hardcoded values and the endpoint we're using on the DigitalOcean API Docs.

type ReqBlob struct {
	Name  string `json:"name"`
	Size  string `json:"size"`
	Image string `json:"image"`
}

func createDrop(name string, DOAPIKEY string) (Droplet, error) {
	reqBlob := ReqBlob{}
	reqBlob.Name = name
	reqBlob.Image = "ubuntu-20-04-x64"
	reqBlob.Size = "s-1vcpu-1gb"
	marshalled, err := json.Marshal(reqBlob)
	
    if err != nil {
		return Droplet{}, err
	}
	
}

We created a ReqBlob object to hold a JSON-parseable representation of the JSON blob we will have to send in the POST request. We then set up the POST request with the makeReq() function we used earlier, tweaked slightly to handle the case of needing to pass POST request data.

func makeReq(method string, apikey string, url string, databytes []byte) (*http.Request, error) {
	req, err := http.NewRequest(method, url, bytes.NewBuffer(databytes))
	if err != nil {
		return &http.Request{}, err
	}
	req.Header.Add("Authorization", "Bearer "+apikey)
	req.Header.Add("Content-Type", "application/json")
	return req, nil
}

func createDrop(name string, DOAPIKEY string) (Droplet, error) {
	reqBlob := ReqBlob{}
	reqBlob.Name = name
	reqBlob.Image = "ubuntu-20-04-x64"
	reqBlob.Size = "s-1vcpu-1gb"
	marshalled, err := json.Marshal(reqBlob)
	fmt.Printf("[-] Creating droplet: %s\n", string(marshalled))
	if err != nil {
		return Droplet{}, err
	}
	req, err := makeReq("POST", DOAPIKEY, BASEURL+"droplets", marshalled)

	if err != nil {
		return Droplet{}, err
	}
	client := http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return Droplet{}, err
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return Droplet{}, err
	}
	
}

We service the request with res, err := client.Do(req) and extract the body using ioutil.ReadAll(res.Body). After that, we need to check to make sure the request returned correctly, parse the body into a container object that holds our droplet data and return the nested droplet data.

type DropletContainer struct {
	Drop Droplet `json:"droplet"`
}
type Droplet struct {
	Id        int    `json:"id"`
	Name      string `json:"name"`
	CreatedAt string `json:"created_at"`
	Image     Img    `json:"image"`
}

func createDrop(name string, DOAPIKEY string) (Droplet, error) {
	reqBlob := ReqBlob{}
	reqBlob.Name = name
	reqBlob.Image = "ubuntu-20-04-x64"
	reqBlob.Size = "s-1vcpu-1gb"
	marshalled, err := json.Marshal(reqBlob)
	fmt.Printf("[-] Creating droplet: %s\n", string(marshalled))
	if err != nil {
		return Droplet{}, err
	}
	req, err := makeReq("POST", DOAPIKEY, BASEURL+"droplets", marshalled)

	if err != nil {
		return Droplet{}, err
	}
	client := http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return Droplet{}, err
	}
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return Droplet{}, err
	}
	if res.StatusCode >= 300 {
		fmt.Printf("[x] There was an error\nStatus code: %d\nody: %s\n", res.StatusCode, string(body))
		err := fmt.Errorf("[x] There was an error, body: %s", string(body))
		return Droplet{}, err
	}

	drop := DropletContainer{}
	json.Unmarshal(body, &drop)
	return drop.Drop, nil
}

DropletContainer is essentially a structure that just holds a Droplet object, because the JSON returned data holds all of the structured Droplet information in the JSON field denoted by the key droplet, so this container object is just to make it a little easier to extract the nested droplet object.

Conclusion

And just like that... you're good to go! You can now programatically create droplets on DigitalOcean. In the next couple articles, we'll talk about deleting droplets, creating custom images and more!