docs/drivers/amazonec2/amazonec2.go

532 lines
11 KiB
Go

package amazonec2
import (
"crypto/md5"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"time"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/docker/machine/drivers"
"github.com/docker/machine/drivers/amazonec2/amz"
"github.com/docker/machine/ssh"
"github.com/docker/machine/state"
)
const (
driverName = "amazonec2"
defaultRegion = "us-east-1"
defaultAMI = "ami-a00461c8"
defaultInstanceType = "t2.micro"
defaultRootSize = 16
ipRange = "0.0.0.0/0"
)
type Driver struct {
Id string
AccessKey string
SecretKey string
Region string
AMI string
SSHKeyID int
KeyName string
InstanceId string
InstanceType string
IPAddress string
SubnetId string
SecurityGroupId string
ReservationId string
RootSize int64
VpcId string
storePath string
keyPath string
}
type CreateFlags struct {
AccessKey *string
SecretKey *string
Region *string
AMI *string
InstanceType *string
SubnetId *string
RootSize *int64
}
func init() {
drivers.Register(driverName, &drivers.RegisteredDriver{
New: NewDriver,
GetCreateFlags: GetCreateFlags,
})
}
func GetCreateFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "amazonec2-access-key",
Usage: "AWS Access Key",
Value: "",
},
cli.StringFlag{
Name: "amazonec2-secret-key",
Usage: "AWS Secret Key",
Value: "",
},
cli.StringFlag{
Name: "amazonec2-ami",
Usage: "AWS machine image",
Value: defaultAMI,
},
cli.StringFlag{
Name: "amazonec2-region",
Usage: "AWS region",
Value: defaultRegion,
},
cli.StringFlag{
Name: "amazonec2-subnet-id",
Usage: "AWS VPC subnet id",
Value: "",
},
cli.StringFlag{
Name: "amazonec2-instance-type",
Usage: "AWS instance type",
Value: defaultInstanceType,
},
cli.IntFlag{
Name: "amazonec2-root-size",
Usage: "AWS root disk size (in GB)",
Value: defaultRootSize,
},
}
}
func NewDriver(storePath string) (drivers.Driver, error) {
id := generateId()
return &Driver{Id: id, storePath: storePath}, nil
}
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.AccessKey = flags.String("amazonec2-access-key")
d.SecretKey = flags.String("amazonec2-secret-key")
d.AMI = flags.String("amazonec2-ami")
d.Region = flags.String("amazonec2-region")
d.InstanceType = flags.String("amazonec2-instance-type")
d.SubnetId = flags.String("amazonec2-subnet-id")
d.RootSize = int64(flags.Int("amazonec2-root-size"))
if d.AccessKey == "" {
return fmt.Errorf("amazonec2 driver requires the --amazonec2-access-key option")
}
if d.SecretKey == "" {
return fmt.Errorf("amazonec2 driver requires the --amazonec2-secret-key option")
}
if d.SubnetId == "" {
return fmt.Errorf("amazonec2 driver requires the --amazonec2-subnet-id option")
}
return nil
}
func (d *Driver) DriverName() string {
return driverName
}
func (d *Driver) Create() error {
log.Infof("Launching instance...")
if err := d.createKeyPair(); err != nil {
fmt.Errorf("unable to create key pair: %s", err)
}
group, err := d.createSecurityGroup()
if err != nil {
return err
}
bdm := &amz.BlockDeviceMapping{
DeviceName: "/dev/sda1",
VolumeSize: d.RootSize,
DeleteOnTermination: true,
VolumeType: "gp2",
}
log.Debugf("launching instance")
instance, err := d.getClient().RunInstance(d.AMI, d.InstanceType, "a", 1, 1, group.GroupId, d.KeyName, d.SubnetId, bdm)
if err != nil {
return fmt.Errorf("Error launching instance: %s", err)
}
d.InstanceId = instance.InstanceId
d.waitForInstance()
log.Debugf("created instance ID %s, IP address %s",
d.InstanceId,
d.IPAddress)
log.Infof("Waiting for SSH...")
if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", d.IPAddress, 22)); err != nil {
return err
}
log.Debugf("Installing Docker")
cmd, err := d.GetSSHCommand("if [ ! -e /usr/bin/docker ]; then curl get.docker.io | sudo sh -; fi")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
cmd, err = d.GetSSHCommand("sudo stop docker")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
log.Debugf("HACK: Downloading version of Docker with identity auth...")
cmd, err = d.GetSSHCommand("sudo curl -sS -o /usr/bin/docker https://bfirsh.s3.amazonaws.com/docker/docker-1.3.1-dev-identity-auth")
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 --auth-authorized-dir=/root/.docker/authorized-keys.d\"' | sudo tee -a /etc/default/docker")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
// HACK: create dir for ubuntu user to access
log.Debugf("Adding key to authorized-keys.d...")
cmd, err = d.GetSSHCommand("sudo mkdir -p /root/.docker && sudo chown -R ubuntu /root/.docker")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
f, err := os.Open(filepath.Join(os.Getenv("HOME"), ".docker/public-key.json"))
if err != nil {
return err
}
defer f.Close()
cmdString := fmt.Sprintf("sudo mkdir -p %q && sudo tee -a %q", "/root/.docker/authorized-keys.d", "/root/.docker/authorized-keys.d/docker-host.json")
cmd, err = d.GetSSHCommand(cmdString)
if err != nil {
return err
}
cmd.Stdin = f
if err := cmd.Run(); err != nil {
return err
}
cmd, err = d.GetSSHCommand("sudo start docker")
if err != nil {
return err
}
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) GetURL() (string, error) {
ip, err := d.GetIP()
if err != nil {
return "", err
}
if ip == "" {
return "", nil
}
return fmt.Sprintf("tcp://%s:2376", ip), nil
}
func (d *Driver) GetIP() (string, error) {
inst, err := d.getInstance()
if err != nil {
return "", err
}
d.IPAddress = inst.IpAddress
return d.IPAddress, nil
}
func (d *Driver) GetState() (state.State, error) {
inst, err := d.getInstance()
if err != nil {
return state.Error, err
}
switch inst.InstanceState.Name {
case "pending":
return state.Starting, nil
case "running":
return state.Running, nil
case "stopping":
return state.Stopping, nil
case "shutting-down":
return state.Stopping, nil
case "stopped":
return state.Stopped, nil
}
return state.None, nil
}
func (d *Driver) Start() error {
if err := d.getClient().StartInstance(d.InstanceId); err != nil {
return err
}
if err := d.waitForInstance(); err != nil {
return err
}
if err := d.updateDriver(); err != nil {
return err
}
return nil
}
func (d *Driver) Stop() error {
if err := d.getClient().StopInstance(d.InstanceId, false); err != nil {
return err
}
return nil
}
func (d *Driver) Remove() error {
if err := d.terminate(); err != nil {
return fmt.Errorf("unabme to terminate instance: %s", err)
}
// wait until terminated so we can remove security group
for {
st, err := d.GetState()
if err != nil {
break
}
if st == state.None {
break
}
time.Sleep(1 * time.Second)
}
if err := d.deleteSecurityGroup(); err != nil {
return fmt.Errorf("unable to remove security group: %s", err)
}
// remove keypair
if err := d.deleteKeyPair(); err != nil {
return fmt.Errorf("unable to remove key pair: %s", err)
}
return nil
}
func (d *Driver) Restart() error {
if err := d.getClient().RestartInstance(d.InstanceId); err != nil {
return fmt.Errorf("unable to restart instance: %s", err)
}
return nil
}
func (d *Driver) Kill() error {
if err := d.getClient().StopInstance(d.InstanceId, true); err != nil {
return err
}
return nil
}
func (d *Driver) Upgrade() error {
return fmt.Errorf("unable to upgrade as we are using the custom docker binary with identity auth")
}
func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) {
return ssh.GetSSHCommand(d.IPAddress, 22, "ubuntu", d.sshKeyPath(), args...), nil
}
func (d *Driver) getClient() *amz.EC2 {
auth := amz.GetAuth(d.AccessKey, d.SecretKey)
return amz.NewEC2(auth, d.Region)
}
func (d *Driver) sshKeyPath() string {
return path.Join(d.storePath, "id_rsa")
}
func (d *Driver) updateDriver() error {
inst, err := d.getInstance()
if err != nil {
return err
}
d.InstanceId = inst.InstanceId
d.IPAddress = inst.IpAddress
return nil
}
func (d *Driver) publicSSHKeyPath() string {
return d.sshKeyPath() + ".pub"
}
func (d *Driver) getInstance() (*amz.EC2Instance, error) {
instance, err := d.getClient().GetInstance(d.InstanceId)
if err != nil {
return nil, err
}
return &instance, nil
}
func (d *Driver) waitForInstance() error {
for {
st, err := d.GetState()
if err != nil {
return err
}
if st == state.Running {
break
}
time.Sleep(1 * time.Second)
}
if err := d.updateDriver(); err != nil {
return err
}
return nil
}
func (d *Driver) createKeyPair() error {
if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil {
return err
}
publicKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
if err != nil {
return err
}
keyName := fmt.Sprintf("docker-machine-%s", d.Id)
log.Debugf("creating key pair: %s", keyName)
if err := d.getClient().ImportKeyPair(keyName, string(publicKey)); err != nil {
return err
}
d.KeyName = keyName
return nil
}
func (d *Driver) terminate() error {
if d.InstanceId == "" {
return fmt.Errorf("unknown instance")
}
log.Debugf("terminating instance: %s", d.InstanceId)
if err := d.getClient().TerminateInstance(d.InstanceId); err != nil {
return fmt.Errorf("unable to terminate instance: %s", err)
}
return nil
}
func (d *Driver) createSecurityGroup() (*amz.SecurityGroup, error) {
subnets, err := d.getClient().GetSubnets()
if err != nil {
return nil, err
}
vpcId := subnets[0].VpcId
d.VpcId = vpcId
log.Debugf("creating security group in %s", d.VpcId)
grpName := fmt.Sprintf("docker-machine-%s", d.Id)
group, err := d.getClient().CreateSecurityGroup(grpName, "Docker Machine", d.VpcId)
if err != nil {
return nil, err
}
d.SecurityGroupId = group.GroupId
perms := []amz.IpPermission{
{
Protocol: "tcp",
FromPort: 22,
ToPort: 22,
IpRange: ipRange,
},
{
Protocol: "tcp",
FromPort: 2376,
ToPort: 2376,
IpRange: ipRange,
},
}
log.Debugf("authorizing %s", ipRange)
if err := d.getClient().AuthorizeSecurityGroup(d.SecurityGroupId, perms); err != nil {
return nil, err
}
return group, nil
}
func (d *Driver) deleteSecurityGroup() error {
log.Debugf("deleting security group %s", d.SecurityGroupId)
if err := d.getClient().DeleteSecurityGroup(d.SecurityGroupId); err != nil {
return err
}
return nil
}
func (d *Driver) deleteKeyPair() error {
log.Debugf("deleting key pair: %s", d.KeyName)
if err := d.getClient().DeleteKeyPair(d.KeyName); err != nil {
return err
}
return nil
}
func generateId() string {
rb := make([]byte, 10)
_, err := rand.Read(rb)
if err != nil {
log.Fatalf("unable to generate id: %s", err)
}
h := md5.New()
io.WriteString(h, string(rb))
return fmt.Sprintf("%x", h.Sum(nil))
}