package digitalocean import ( "fmt" "io/ioutil" "os/exec" "path/filepath" "time" "code.google.com/p/goauth2/oauth" log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" "github.com/digitalocean/godo" "github.com/docker/machine/drivers" "github.com/docker/machine/ssh" "github.com/docker/machine/state" ) const ( dockerConfigDir = "/etc/docker" ) type Driver struct { AccessToken string DropletID int DropletName string Image string MachineName string IPAddress string Region string SSHKeyID int Size string CaCertPath string PrivateKeyPath string DriverKeyPath string SwarmMaster bool SwarmHost string SwarmDiscovery string storePath string } func init() { drivers.Register("digitalocean", &drivers.RegisteredDriver{ New: NewDriver, GetCreateFlags: GetCreateFlags, }) } // GetCreateFlags registers the flags this driver adds to // "docker hosts create" func GetCreateFlags() []cli.Flag { return []cli.Flag{ cli.StringFlag{ EnvVar: "DIGITALOCEAN_ACCESS_TOKEN", Name: "digitalocean-access-token", Usage: "Digital Ocean access token", }, cli.StringFlag{ EnvVar: "DIGITALOCEAN_IMAGE", Name: "digitalocean-image", Usage: "Digital Ocean Image", Value: "ubuntu-14-04-x64", }, cli.StringFlag{ EnvVar: "DIGITALOCEAN_REGION", Name: "digitalocean-region", Usage: "Digital Ocean region", Value: "nyc3", }, cli.StringFlag{ EnvVar: "DIGITALOCEAN_SIZE", Name: "digitalocean-size", Usage: "Digital Ocean size", Value: "512mb", }, } } func NewDriver(machineName string, storePath string, caCert string, privateKey string) (drivers.Driver, error) { return &Driver{MachineName: machineName, storePath: storePath, CaCertPath: caCert, PrivateKeyPath: privateKey}, nil } 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.SwarmMaster = flags.Bool("swarm-master") d.SwarmHost = flags.String("swarm-host") d.SwarmDiscovery = flags.String("swarm-discovery") if d.AccessToken == "" { return fmt.Errorf("digitalocean driver requires the --digitalocean-access-token option") } return nil } func (d *Driver) PreCreateCheck() error { return nil } func (d *Driver) Create() error { 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: d.Image, Name: d.MachineName, Region: d.Region, Size: d.Size, SSHKeys: []interface{}{d.SSHKeyID}, } newDroplet, _, err := client.Droplets.Create(createRequest) if err != nil { return err } d.DropletID = newDroplet.Droplet.ID for { newDroplet, _, err = client.Droplets.Get(d.DropletID) if err != nil { return err } for _, network := range newDroplet.Droplet.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.Droplet.ID, d.IPAddress) log.Infof("Waiting for SSH...") if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", d.IPAddress, 22)); err != nil { return err } log.Info("Configuring Machine...") log.Debugf("Setting hostname: %s", d.MachineName) cmd, err := d.GetSSHCommand(fmt.Sprintf( "echo \"127.0.0.1 %s\" | sudo tee -a /etc/hosts && sudo hostname %s && echo \"%s\" | sudo tee /etc/hostname", d.MachineName, d.MachineName, d.MachineName, )) if err != nil { return err } if err := cmd.Run(); err != nil { return err } return nil } func (d *Driver) createSSHKey() (*godo.Key, error) { if err := ssh.GenerateSSHKey(d.sshKeyPath()); 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:2376", ip), nil } func (d *Driver) GetIP() (string, error) { if d.IPAddress == "" { return "", fmt.Errorf("IP address is not set") } return d.IPAddress, 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.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) StartDocker() error { log.Debug("Starting Docker...") cmd, err := d.GetSSHCommand("sudo service docker start") if err != nil { return err } if err := cmd.Run(); err != nil { return err } return nil } func (d *Driver) StopDocker() error { log.Debug("Stopping Docker...") cmd, err := d.GetSSHCommand("sudo service docker stop") if err != nil { return err } if err := cmd.Run(); err != nil { return err } return nil } func (d *Driver) GetDockerConfigDir() string { return dockerConfigDir } func (d *Driver) Upgrade() error { log.Debugf("Upgrading Docker") cmd, err := d.GetSSHCommand("sudo apt-get update && sudo apt-get install --upgrade lxc-docker") if err != nil { return err } if err := cmd.Run(); err != nil { return err } return cmd.Run() } func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { cmd := ssh.GetSSHCommand(d.IPAddress, 22, "root", d.sshKeyPath(), args...) return cmd, nil } func (d *Driver) getClient() *godo.Client { t := &oauth.Transport{ Token: &oauth.Token{AccessToken: d.AccessToken}, } return godo.NewClient(t.Client()) } func (d *Driver) sshKeyPath() string { return filepath.Join(d.storePath, "id_rsa") } func (d *Driver) publicSSHKeyPath() string { return d.sshKeyPath() + ".pub" }