mirror of https://github.com/docker/docs.git
				
				
				
			
		
			
				
	
	
		
			325 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
| package digitalocean
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/digitalocean/godo"
 | |
| 	"github.com/docker/machine/libmachine/drivers"
 | |
| 	"github.com/docker/machine/libmachine/log"
 | |
| 	"github.com/docker/machine/libmachine/mcnflag"
 | |
| 	"github.com/docker/machine/libmachine/ssh"
 | |
| 	"github.com/docker/machine/libmachine/state"
 | |
| 	"golang.org/x/oauth2"
 | |
| )
 | |
| 
 | |
| type Driver struct {
 | |
| 	*drivers.BaseDriver
 | |
| 	AccessToken       string
 | |
| 	DropletID         int
 | |
| 	DropletName       string
 | |
| 	Image             string
 | |
| 	Region            string
 | |
| 	SSHKeyID          int
 | |
| 	Size              string
 | |
| 	IPv6              bool
 | |
| 	Backups           bool
 | |
| 	PrivateNetworking bool
 | |
| 	UserDataFile      string
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	defaultImage  = "ubuntu-15-10-x64"
 | |
| 	defaultRegion = "nyc3"
 | |
| 	defaultSize   = "512mb"
 | |
| )
 | |
| 
 | |
| // GetCreateFlags registers the flags this driver adds to
 | |
| // "docker hosts create"
 | |
| func (d *Driver) GetCreateFlags() []mcnflag.Flag {
 | |
| 	return []mcnflag.Flag{
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_ACCESS_TOKEN",
 | |
| 			Name:   "digitalocean-access-token",
 | |
| 			Usage:  "Digital Ocean access token",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_SSH_USER",
 | |
| 			Name:   "digitalocean-ssh-user",
 | |
| 			Usage:  "Digital Ocean SSH username",
 | |
| 			Value:  "root",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_IMAGE",
 | |
| 			Name:   "digitalocean-image",
 | |
| 			Usage:  "Digital Ocean Image",
 | |
| 			Value:  defaultImage,
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_REGION",
 | |
| 			Name:   "digitalocean-region",
 | |
| 			Usage:  "Digital Ocean region",
 | |
| 			Value:  defaultRegion,
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_SIZE",
 | |
| 			Name:   "digitalocean-size",
 | |
| 			Usage:  "Digital Ocean size",
 | |
| 			Value:  defaultSize,
 | |
| 		},
 | |
| 		mcnflag.BoolFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_IPV6",
 | |
| 			Name:   "digitalocean-ipv6",
 | |
| 			Usage:  "enable ipv6 for droplet",
 | |
| 		},
 | |
| 		mcnflag.BoolFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_PRIVATE_NETWORKING",
 | |
| 			Name:   "digitalocean-private-networking",
 | |
| 			Usage:  "enable private networking for droplet",
 | |
| 		},
 | |
| 		mcnflag.BoolFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_BACKUPS",
 | |
| 			Name:   "digitalocean-backups",
 | |
| 			Usage:  "enable backups for droplet",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "DIGITALOCEAN_USERDATA",
 | |
| 			Name:   "digitalocean-userdata",
 | |
| 			Usage:  "path to file with cloud-init user-data",
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func NewDriver(hostName, storePath string) *Driver {
 | |
| 	return &Driver{
 | |
| 		Image:  defaultImage,
 | |
| 		Size:   defaultSize,
 | |
| 		Region: defaultRegion,
 | |
| 		BaseDriver: &drivers.BaseDriver{
 | |
| 			MachineName: hostName,
 | |
| 			StorePath:   storePath,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (d *Driver) GetSSHHostname() (string, error) {
 | |
| 	return d.GetIP()
 | |
| }
 | |
| 
 | |
| // DriverName returns the name of the driver
 | |
| func (d *Driver) DriverName() string {
 | |
| 	return "digitalocean"
 | |
| }
 | |
| 
 | |
| func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
 | |
| 	d.AccessToken = flags.String("digitalocean-access-token")
 | |
| 	d.Image = flags.String("digitalocean-image")
 | |
| 	d.Region = flags.String("digitalocean-region")
 | |
| 	d.Size = flags.String("digitalocean-size")
 | |
| 	d.IPv6 = flags.Bool("digitalocean-ipv6")
 | |
| 	d.PrivateNetworking = flags.Bool("digitalocean-private-networking")
 | |
| 	d.Backups = flags.Bool("digitalocean-backups")
 | |
| 	d.UserDataFile = flags.String("digitalocean-userdata")
 | |
| 	d.SwarmMaster = flags.Bool("swarm-master")
 | |
| 	d.SwarmHost = flags.String("swarm-host")
 | |
| 	d.SwarmDiscovery = flags.String("swarm-discovery")
 | |
| 	d.SSHUser = flags.String("digitalocean-ssh-user")
 | |
| 	d.SSHPort = 22
 | |
| 
 | |
| 	if d.AccessToken == "" {
 | |
| 		return fmt.Errorf("digitalocean driver requires the --digitalocean-access-token option")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) PreCreateCheck() error {
 | |
| 	if d.UserDataFile != "" {
 | |
| 		if _, err := os.Stat(d.UserDataFile); os.IsNotExist(err) {
 | |
| 			return fmt.Errorf("user-data file %s could not be found", d.UserDataFile)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	client := d.getClient()
 | |
| 	regions, _, err := client.Regions.List(nil)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	for _, region := range regions {
 | |
| 		if region.Slug == d.Region {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Errorf("digitalocean requires a valid region")
 | |
| }
 | |
| 
 | |
| func (d *Driver) Create() error {
 | |
| 	var userdata string
 | |
| 	if d.UserDataFile != "" {
 | |
| 		buf, err := ioutil.ReadFile(d.UserDataFile)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		userdata = string(buf)
 | |
| 	}
 | |
| 
 | |
| 	log.Infof("Creating SSH key...")
 | |
| 
 | |
| 	key, err := d.createSSHKey()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	d.SSHKeyID = key.ID
 | |
| 
 | |
| 	log.Infof("Creating Digital Ocean droplet...")
 | |
| 
 | |
| 	client := d.getClient()
 | |
| 
 | |
| 	createRequest := &godo.DropletCreateRequest{
 | |
| 		Image:             godo.DropletCreateImage{Slug: d.Image},
 | |
| 		Name:              d.MachineName,
 | |
| 		Region:            d.Region,
 | |
| 		Size:              d.Size,
 | |
| 		IPv6:              d.IPv6,
 | |
| 		PrivateNetworking: d.PrivateNetworking,
 | |
| 		Backups:           d.Backups,
 | |
| 		UserData:          userdata,
 | |
| 		SSHKeys:           []godo.DropletCreateSSHKey{{ID: d.SSHKeyID}},
 | |
| 	}
 | |
| 
 | |
| 	newDroplet, _, err := client.Droplets.Create(createRequest)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	d.DropletID = newDroplet.ID
 | |
| 
 | |
| 	log.Info("Waiting for IP address to be assigned to the Droplet...")
 | |
| 	for {
 | |
| 		newDroplet, _, err = client.Droplets.Get(d.DropletID)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		for _, network := range newDroplet.Networks.V4 {
 | |
| 			if network.Type == "public" {
 | |
| 				d.IPAddress = network.IPAddress
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if d.IPAddress != "" {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		time.Sleep(1 * time.Second)
 | |
| 	}
 | |
| 
 | |
| 	log.Debugf("Created droplet ID %d, IP address %s",
 | |
| 		newDroplet.ID,
 | |
| 		d.IPAddress)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) createSSHKey() (*godo.Key, error) {
 | |
| 	if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	publicKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	createRequest := &godo.KeyCreateRequest{
 | |
| 		Name:      d.MachineName,
 | |
| 		PublicKey: string(publicKey),
 | |
| 	}
 | |
| 
 | |
| 	key, _, err := d.getClient().Keys.Create(createRequest)
 | |
| 	if err != nil {
 | |
| 		return key, err
 | |
| 	}
 | |
| 
 | |
| 	return key, nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) GetURL() (string, error) {
 | |
| 	ip, err := d.GetIP()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) GetState() (state.State, error) {
 | |
| 	droplet, _, err := d.getClient().Droplets.Get(d.DropletID)
 | |
| 	if err != nil {
 | |
| 		return state.Error, err
 | |
| 	}
 | |
| 	switch droplet.Status {
 | |
| 	case "new":
 | |
| 		return state.Starting, nil
 | |
| 	case "active":
 | |
| 		return state.Running, nil
 | |
| 	case "off":
 | |
| 		return state.Stopped, nil
 | |
| 	}
 | |
| 	return state.None, nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Start() error {
 | |
| 	_, _, err := d.getClient().DropletActions.PowerOn(d.DropletID)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (d *Driver) Stop() error {
 | |
| 	_, _, err := d.getClient().DropletActions.Shutdown(d.DropletID)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (d *Driver) Remove() error {
 | |
| 	client := d.getClient()
 | |
| 	if resp, err := client.Keys.DeleteByID(d.SSHKeyID); err != nil {
 | |
| 		if resp.StatusCode == 404 {
 | |
| 			log.Infof("Digital Ocean SSH key doesn't exist, assuming it is already deleted")
 | |
| 		} else {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	if resp, err := client.Droplets.Delete(d.DropletID); err != nil {
 | |
| 		if resp.StatusCode == 404 {
 | |
| 			log.Infof("Digital Ocean droplet doesn't exist, assuming it is already deleted")
 | |
| 		} else {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Restart() error {
 | |
| 	_, _, err := d.getClient().DropletActions.Reboot(d.DropletID)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (d *Driver) Kill() error {
 | |
| 	_, _, err := d.getClient().DropletActions.PowerOff(d.DropletID)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (d *Driver) getClient() *godo.Client {
 | |
| 	token := &oauth2.Token{AccessToken: d.AccessToken}
 | |
| 	tokenSource := oauth2.StaticTokenSource(token)
 | |
| 	client := oauth2.NewClient(oauth2.NoContext, tokenSource)
 | |
| 
 | |
| 	return godo.NewClient(client)
 | |
| }
 | |
| 
 | |
| func (d *Driver) publicSSHKeyPath() string {
 | |
| 	return d.GetSSHKeyPath() + ".pub"
 | |
| }
 |