mirror of https://github.com/docker/docs.git
				
				
				
			
		
			
				
	
	
		
			459 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
| package exoscale
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net"
 | |
| 	"strings"
 | |
| 	"text/template"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/docker/machine/libmachine/drivers"
 | |
| 	"github.com/docker/machine/libmachine/log"
 | |
| 	"github.com/docker/machine/libmachine/mcnflag"
 | |
| 	"github.com/docker/machine/libmachine/mcnutils"
 | |
| 	"github.com/docker/machine/libmachine/state"
 | |
| 	"github.com/pyr/egoscale/src/egoscale"
 | |
| )
 | |
| 
 | |
| type Driver struct {
 | |
| 	*drivers.BaseDriver
 | |
| 	URL              string
 | |
| 	APIKey           string `json:"ApiKey"`
 | |
| 	APISecretKey     string `json:"ApiSecretKey"`
 | |
| 	InstanceProfile  string
 | |
| 	DiskSize         int
 | |
| 	Image            string
 | |
| 	SecurityGroup    string
 | |
| 	AvailabilityZone string
 | |
| 	KeyPair          string
 | |
| 	PublicKey        string
 | |
| 	ID               string `json:"Id"`
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	defaultInstanceProfile  = "small"
 | |
| 	defaultDiskSize         = 50
 | |
| 	defaultImage            = "ubuntu-15.10"
 | |
| 	defaultAvailabilityZone = "ch-gva-2"
 | |
| )
 | |
| 
 | |
| // GetCreateFlags registers the flags this driver adds to
 | |
| // "docker hosts create"
 | |
| func (d *Driver) GetCreateFlags() []mcnflag.Flag {
 | |
| 	return []mcnflag.Flag{
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXOSCALE_ENDPOINT",
 | |
| 			Name:   "exoscale-url",
 | |
| 			Usage:  "exoscale API endpoint",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXOSCALE_API_KEY",
 | |
| 			Name:   "exoscale-api-key",
 | |
| 			Usage:  "exoscale API key",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXOSCALE_API_SECRET",
 | |
| 			Name:   "exoscale-api-secret-key",
 | |
| 			Usage:  "exoscale API secret key",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXOSCALE_INSTANCE_PROFILE",
 | |
| 			Name:   "exoscale-instance-profile",
 | |
| 			Value:  defaultInstanceProfile,
 | |
| 			Usage:  "exoscale instance profile (small, medium, large, ...)",
 | |
| 		},
 | |
| 		mcnflag.IntFlag{
 | |
| 			EnvVar: "EXOSCALE_DISK_SIZE",
 | |
| 			Name:   "exoscale-disk-size",
 | |
| 			Value:  defaultDiskSize,
 | |
| 			Usage:  "exoscale disk size (10, 50, 100, 200, 400)",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXSOCALE_IMAGE",
 | |
| 			Name:   "exoscale-image",
 | |
| 			Value:  defaultImage,
 | |
| 			Usage:  "exoscale image template",
 | |
| 		},
 | |
| 		mcnflag.StringSliceFlag{
 | |
| 			EnvVar: "EXOSCALE_SECURITY_GROUP",
 | |
| 			Name:   "exoscale-security-group",
 | |
| 			Value:  []string{},
 | |
| 			Usage:  "exoscale security group",
 | |
| 		},
 | |
| 		mcnflag.StringFlag{
 | |
| 			EnvVar: "EXOSCALE_AVAILABILITY_ZONE",
 | |
| 			Name:   "exoscale-availability-zone",
 | |
| 			Value:  defaultAvailabilityZone,
 | |
| 			Usage:  "exoscale availibility zone",
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func NewDriver(hostName, storePath string) drivers.Driver {
 | |
| 	return &Driver{
 | |
| 		InstanceProfile:  defaultInstanceProfile,
 | |
| 		DiskSize:         defaultDiskSize,
 | |
| 		Image:            defaultImage,
 | |
| 		AvailabilityZone: defaultAvailabilityZone,
 | |
| 		BaseDriver: &drivers.BaseDriver{
 | |
| 			MachineName: hostName,
 | |
| 			StorePath:   storePath,
 | |
| 		},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (d *Driver) GetSSHHostname() (string, error) {
 | |
| 	return d.GetIP()
 | |
| }
 | |
| 
 | |
| func (d *Driver) GetSSHUsername() string {
 | |
| 	return "ubuntu"
 | |
| }
 | |
| 
 | |
| // DriverName returns the name of the driver
 | |
| func (d *Driver) DriverName() string {
 | |
| 	return "exoscale"
 | |
| }
 | |
| 
 | |
| func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
 | |
| 	d.URL = flags.String("exoscale-endpoint")
 | |
| 	d.APIKey = flags.String("exoscale-api-key")
 | |
| 	d.APISecretKey = flags.String("exoscale-api-secret-key")
 | |
| 	d.InstanceProfile = flags.String("exoscale-instance-profile")
 | |
| 	d.DiskSize = flags.Int("exoscale-disk-size")
 | |
| 	d.Image = flags.String("exoscale-image")
 | |
| 	securityGroups := flags.StringSlice("exoscale-security-group")
 | |
| 	if len(securityGroups) == 0 {
 | |
| 		securityGroups = []string{"docker-machine"}
 | |
| 	}
 | |
| 	d.SecurityGroup = strings.Join(securityGroups, ",")
 | |
| 	d.AvailabilityZone = flags.String("exoscale-availability-zone")
 | |
| 	d.SwarmMaster = flags.Bool("swarm-master")
 | |
| 	d.SwarmHost = flags.String("swarm-host")
 | |
| 	d.SwarmDiscovery = flags.String("swarm-discovery")
 | |
| 
 | |
| 	if d.URL == "" {
 | |
| 		d.URL = "https://api.exoscale.ch/compute"
 | |
| 	}
 | |
| 	if d.APIKey == "" || d.APISecretKey == "" {
 | |
| 		return fmt.Errorf("Please specify an API key (--exoscale-api-key) and an API secret key (--exoscale-api-secret-key).")
 | |
| 	}
 | |
| 
 | |
| 	return 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) {
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 	vm, err := client.GetVirtualMachine(d.ID)
 | |
| 	if err != nil {
 | |
| 		return state.Error, err
 | |
| 	}
 | |
| 	switch vm.State {
 | |
| 	case "Starting":
 | |
| 		return state.Starting, nil
 | |
| 	case "Running":
 | |
| 		return state.Running, nil
 | |
| 	case "Stopping":
 | |
| 		return state.Running, nil
 | |
| 	case "Stopped":
 | |
| 		return state.Stopped, nil
 | |
| 	case "Destroyed":
 | |
| 		return state.Stopped, nil
 | |
| 	case "Expunging":
 | |
| 		return state.Stopped, nil
 | |
| 	case "Migrating":
 | |
| 		return state.Paused, nil
 | |
| 	case "Error":
 | |
| 		return state.Error, nil
 | |
| 	case "Unknown":
 | |
| 		return state.Error, nil
 | |
| 	case "Shutdowned":
 | |
| 		return state.Stopped, nil
 | |
| 	}
 | |
| 	return state.None, nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) createDefaultSecurityGroup(client *egoscale.Client, group string) (string, error) {
 | |
| 	rules := []egoscale.SecurityGroupRule{
 | |
| 		{
 | |
| 			SecurityGroupId: "",
 | |
| 			Cidr:            "0.0.0.0/0",
 | |
| 			Protocol:        "TCP",
 | |
| 			Port:            22,
 | |
| 		},
 | |
| 		{
 | |
| 			SecurityGroupId: "",
 | |
| 			Cidr:            "0.0.0.0/0",
 | |
| 			Protocol:        "TCP",
 | |
| 			Port:            2376,
 | |
| 		},
 | |
| 		{
 | |
| 			SecurityGroupId: "",
 | |
| 			Cidr:            "0.0.0.0/0",
 | |
| 			Protocol:        "TCP",
 | |
| 			Port:            3376,
 | |
| 		},
 | |
| 		{
 | |
| 			SecurityGroupId: "",
 | |
| 			Cidr:            "0.0.0.0/0",
 | |
| 			Protocol:        "ICMP",
 | |
| 			IcmpType:        8,
 | |
| 			IcmpCode:        0,
 | |
| 		},
 | |
| 	}
 | |
| 	sgresp, err := client.CreateSecurityGroupWithRules(
 | |
| 		group,
 | |
| 		rules,
 | |
| 		make([]egoscale.SecurityGroupRule, 0, 0))
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	sg := sgresp.Id
 | |
| 	return sg, nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Create() error {
 | |
| 	log.Infof("Querying exoscale for the requested parameters...")
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 	topology, err := client.GetTopology()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Availability zone UUID
 | |
| 	zone, ok := topology.Zones[d.AvailabilityZone]
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("Availability zone %v doesn't exist",
 | |
| 			d.AvailabilityZone)
 | |
| 	}
 | |
| 	log.Debugf("Availability zone %v = %s", d.AvailabilityZone, zone)
 | |
| 
 | |
| 	// Image UUID
 | |
| 	var tpl string
 | |
| 	images, ok := topology.Images[strings.ToLower(d.Image)]
 | |
| 	if ok {
 | |
| 		tpl, ok = images[d.DiskSize]
 | |
| 	}
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("Unable to find image %v with size %d",
 | |
| 			d.Image, d.DiskSize)
 | |
| 	}
 | |
| 	log.Debugf("Image %v(%d) = %s", d.Image, d.DiskSize, tpl)
 | |
| 
 | |
| 	// Profile UUID
 | |
| 	profile, ok := topology.Profiles[strings.ToLower(d.InstanceProfile)]
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("Unable to find the %s profile",
 | |
| 			d.InstanceProfile)
 | |
| 	}
 | |
| 	log.Debugf("Profile %v = %s", d.InstanceProfile, profile)
 | |
| 
 | |
| 	// Security groups
 | |
| 	securityGroups := strings.Split(d.SecurityGroup, ",")
 | |
| 	sgs := make([]string, len(securityGroups))
 | |
| 	for idx, group := range securityGroups {
 | |
| 		sg, ok := topology.SecurityGroups[group]
 | |
| 		if !ok {
 | |
| 			log.Infof("Security group %v does not exist, create it",
 | |
| 				group)
 | |
| 			sg, err = d.createDefaultSecurityGroup(client, group)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 		log.Debugf("Security group %v = %s", group, sg)
 | |
| 		sgs[idx] = sg
 | |
| 	}
 | |
| 
 | |
| 	log.Infof("Generate an SSH keypair...")
 | |
| 	keypairName := fmt.Sprintf("docker-machine-%s", d.MachineName)
 | |
| 	kpresp, err := client.CreateKeypair(keypairName)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	err = ioutil.WriteFile(d.GetSSHKeyPath(), []byte(kpresp.Privatekey), 0600)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	d.KeyPair = keypairName
 | |
| 
 | |
| 	log.Infof("Spawn exoscale host...")
 | |
| 
 | |
| 	userdata, err := d.getCloudInit()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	log.Debugf("Using the following cloud-init file:")
 | |
| 	log.Debugf("%s", userdata)
 | |
| 
 | |
| 	machineProfile := egoscale.MachineProfile{
 | |
| 		Template:        tpl,
 | |
| 		ServiceOffering: profile,
 | |
| 		SecurityGroups:  sgs,
 | |
| 		Userdata:        userdata,
 | |
| 		Zone:            zone,
 | |
| 		Keypair:         d.KeyPair,
 | |
| 		Name:            d.MachineName,
 | |
| 	}
 | |
| 
 | |
| 	cvmresp, err := client.CreateVirtualMachine(machineProfile)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	vm, err := d.waitForVM(client, cvmresp)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	d.IPAddress = vm.Nic[0].Ipaddress
 | |
| 	d.ID = vm.Id
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Start() error {
 | |
| 	vmstate, err := d.GetState()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if vmstate == state.Running || vmstate == state.Starting {
 | |
| 		log.Infof("Host is already running or starting")
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 	svmresp, err := client.StartVirtualMachine(d.ID)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = d.waitForJob(client, svmresp); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Stop() error {
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 
 | |
| 	svmresp, err := client.StopVirtualMachine(d.ID)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return d.waitForJob(client, svmresp)
 | |
| }
 | |
| 
 | |
| func (d *Driver) Remove() error {
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 
 | |
| 	// Destroy the SSH key
 | |
| 	if _, err := client.DeleteKeypair(d.KeyPair); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Destroy the virtual machine
 | |
| 	dvmresp, err := client.DestroyVirtualMachine(d.ID)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = d.waitForJob(client, dvmresp); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Restart() error {
 | |
| 	vmstate, err := d.GetState()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if vmstate == state.Stopped {
 | |
| 		return fmt.Errorf("Host is stopped, use start command to start it")
 | |
| 	}
 | |
| 
 | |
| 	client := egoscale.NewClient(d.URL, d.APIKey, d.APISecretKey)
 | |
| 	svmresp, err := client.RebootVirtualMachine(d.ID)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = d.waitForJob(client, svmresp); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) Kill() error {
 | |
| 	return d.Stop()
 | |
| }
 | |
| 
 | |
| func (d *Driver) jobIsDone(client *egoscale.Client, jobid string) (bool, error) {
 | |
| 	resp, err := client.PollAsyncJob(jobid)
 | |
| 	if err != nil {
 | |
| 		return true, err
 | |
| 	}
 | |
| 	switch resp.Jobstatus {
 | |
| 	case 0: // Job is still in progress
 | |
| 	case 1: // Job has successfully completed
 | |
| 		return true, nil
 | |
| 	case 2: // Job has failed to complete
 | |
| 		return true, fmt.Errorf("Operation failed to complete")
 | |
| 	default: // Some other code
 | |
| 	}
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| func (d *Driver) waitForJob(client *egoscale.Client, jobid string) error {
 | |
| 	log.Infof("Waiting for job to complete...")
 | |
| 	return mcnutils.WaitForSpecificOrError(func() (bool, error) {
 | |
| 		return d.jobIsDone(client, jobid)
 | |
| 	}, 60, 2*time.Second)
 | |
| }
 | |
| 
 | |
| func (d *Driver) waitForVM(client *egoscale.Client, jobid string) (*egoscale.DeployVirtualMachineResponse, error) {
 | |
| 	if err := d.waitForJob(client, jobid); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	resp, err := client.PollAsyncJob(jobid)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	vm, err := client.AsyncToVirtualMachine(*resp)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return vm, nil
 | |
| }
 | |
| 
 | |
| // Build a cloud-init user data string that will install and run
 | |
| // docker.
 | |
| func (d *Driver) getCloudInit() (string, error) {
 | |
| 	const tpl = `#cloud-config
 | |
| manage_etc_hosts: true
 | |
| fqdn: {{ .MachineName }}
 | |
| resize_rootfs: true
 | |
| `
 | |
| 	var buffer bytes.Buffer
 | |
| 
 | |
| 	tmpl, err := template.New("cloud-init").Parse(tpl)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	err = tmpl.Execute(&buffer, d)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return buffer.String(), nil
 | |
| }
 |