mirror of https://github.com/docker/docs.git
332 lines
6.9 KiB
Go
332 lines
6.9 KiB
Go
package digitalocean
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"time"
|
|
|
|
"code.google.com/p/goauth2/oauth"
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/digitalocean/godo"
|
|
flag "github.com/docker/docker/pkg/mflag"
|
|
"github.com/docker/docker/utils"
|
|
"github.com/docker/machine/drivers"
|
|
"github.com/docker/machine/ssh"
|
|
"github.com/docker/machine/state"
|
|
)
|
|
|
|
type Driver struct {
|
|
AccessToken string
|
|
DropletID int
|
|
DropletName string
|
|
Image string
|
|
IPAddress string
|
|
Region string
|
|
SSHKeyID int
|
|
Size string
|
|
storePath string
|
|
}
|
|
|
|
type CreateFlags struct {
|
|
AccessToken *string
|
|
Image *string
|
|
Region *string
|
|
Size *string
|
|
}
|
|
|
|
func init() {
|
|
drivers.Register("digitalocean", &drivers.RegisteredDriver{
|
|
New: NewDriver,
|
|
RegisterCreateFlags: RegisterCreateFlags,
|
|
})
|
|
}
|
|
|
|
// RegisterCreateFlags registers the flags this driver adds to
|
|
// "docker hosts create"
|
|
func RegisterCreateFlags(cmd *flag.FlagSet) interface{} {
|
|
createFlags := new(CreateFlags)
|
|
createFlags.AccessToken = cmd.String(
|
|
[]string{"-digitalocean-access-token"},
|
|
"",
|
|
"Digital Ocean access token",
|
|
)
|
|
createFlags.Image = cmd.String(
|
|
[]string{"-digitalocean-image"},
|
|
"docker",
|
|
"Digital Ocean image",
|
|
)
|
|
createFlags.Region = cmd.String(
|
|
[]string{"-digitalocean-region"},
|
|
"nyc3",
|
|
"Digital Ocean region",
|
|
)
|
|
createFlags.Size = cmd.String(
|
|
[]string{"-digitalocean-size"},
|
|
"512mb",
|
|
"Digital Ocean size",
|
|
)
|
|
return createFlags
|
|
}
|
|
|
|
func NewDriver(storePath string) (drivers.Driver, error) {
|
|
return &Driver{storePath: storePath}, nil
|
|
}
|
|
|
|
func (d *Driver) DriverName() string {
|
|
return "digitalocean"
|
|
}
|
|
|
|
func (d *Driver) SetConfigFromFlags(flagsInterface interface{}) error {
|
|
flags := flagsInterface.(*CreateFlags)
|
|
d.AccessToken = *flags.AccessToken
|
|
d.Image = *flags.Image
|
|
d.Region = *flags.Region
|
|
d.Size = *flags.Size
|
|
|
|
if d.AccessToken == "" {
|
|
return fmt.Errorf("digitalocean driver requires the --digitalocean-access-token option")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) Create() error {
|
|
d.setDropletNameIfNotSet()
|
|
|
|
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.DropletName,
|
|
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.Debugf("HACK: Downloading version of Docker with identity auth...")
|
|
|
|
cmd, err := d.GetSSHCommand("stop docker")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err = d.GetSSHCommand("curl -sS https://bfirsh.s3.amazonaws.com/docker/docker-1.3.1-dev-identity-auth > /usr/bin/docker")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Updating /etc/default/docker to use identity auth...")
|
|
|
|
cmd, err = d.GetSSHCommand("echo 'export DOCKER_OPTS=\"--auth=identity --host=tcp://0.0.0.0:2376\"' >> /etc/default/docker")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Adding key to authorized-keys.d...")
|
|
|
|
if err := drivers.AddPublicKeyToAuthorizedHosts(d, "/.docker/authorized-keys.d"); err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err = d.GetSSHCommand("start docker")
|
|
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.DropletName,
|
|
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) Upgrade() error {
|
|
sshCmd, err := d.GetSSHCommand("apt-get update && apt-get install lxc-docker")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sshCmd.Stdin = os.Stdin
|
|
sshCmd.Stdout = os.Stdout
|
|
sshCmd.Stderr = os.Stderr
|
|
if err := sshCmd.Run(); err != nil {
|
|
return fmt.Errorf("%s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
|
|
return ssh.GetSSHCommand(d.IPAddress, 22, "root", d.sshKeyPath(), args...), nil
|
|
}
|
|
|
|
func (d *Driver) setDropletNameIfNotSet() {
|
|
if d.DropletName == "" {
|
|
d.DropletName = fmt.Sprintf("docker-host-%s", utils.GenerateRandomID())
|
|
}
|
|
}
|
|
|
|
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 path.Join(d.storePath, "id_rsa")
|
|
}
|
|
|
|
func (d *Driver) publicSSHKeyPath() string {
|
|
return d.sshKeyPath() + ".pub"
|
|
}
|