github.com/technosophos/deis@v1.7.1-0.20150915173815-f9005256004b/builder/docker/docker.go (about)

     1  package docker
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/Masterminds/cookoo"
    17  	"github.com/Masterminds/cookoo/log"
    18  	"github.com/Masterminds/cookoo/safely"
    19  	"github.com/deis/deis/builder/etcd"
    20  	docli "github.com/fsouza/go-dockerclient"
    21  )
    22  
    23  // Path to the Docker unix socket.
    24  // TODO: When we switch to a newer Docker library, we should favor this:
    25  // 	var DockSock = opts.DefaultUnixSocket
    26  var DockSock = "/var/run/docker.sock"
    27  
    28  // Cleanup removes any existing Docker artifacts.
    29  //
    30  // Returns true if the file exists (and was deleted), or false if no file
    31  // was deleted.
    32  func Cleanup(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    33  	// If info is returned, then the file is there. If we get an error, we're
    34  	// pretty much not going to be able to remove the file (which probably
    35  	// doesn't exist).
    36  	if _, err := os.Stat(DockSock); err == nil {
    37  		log.Infof(c, "Removing leftover docker socket %s", DockSock)
    38  		return true, os.Remove(DockSock)
    39  	}
    40  	return false, nil
    41  }
    42  
    43  // CreateClient creates a new Docker client.
    44  //
    45  // Params:
    46  // 	- url (string): The URI to the Docker daemon. This defaults to the UNIX
    47  // 		socket /var/run/docker.sock.
    48  //
    49  // Returns:
    50  // 	- *docker.Client
    51  //
    52  func CreateClient(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    53  	path := p.Get("url", "unix:///var/run/docker.sock").(string)
    54  
    55  	return docli.NewClient(path)
    56  }
    57  
    58  // Start starts a Docker daemon.
    59  //
    60  // This assumes the presence of the docker client on the host. It does not use
    61  // the API.
    62  func Start(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
    63  	// Allow insecure Docker registries on all private network ranges in RFC 1918 and RFC 6598.
    64  	dargs := []string{
    65  		"-d",
    66  		"--bip=172.19.42.1/16",
    67  		"--insecure-registry",
    68  		"10.0.0.0/8",
    69  		"--insecure-registry",
    70  		"172.16.0.0/12",
    71  		"--insecure-registry",
    72  		"192.168.0.0/16",
    73  		"--insecure-registry",
    74  		"100.64.0.0/10",
    75  	}
    76  
    77  	// For overlay-ish filesystems, force the overlay to kick in if it exists.
    78  	// Then we can check the fstype.
    79  	if err := os.MkdirAll("/", 0700); err == nil {
    80  
    81  		cmd := exec.Command("findmnt", "--noheadings", "--output", "FSTYPE", "--target", "/")
    82  
    83  		if out, err := cmd.Output(); err == nil && strings.TrimSpace(string(out)) == "overlay" {
    84  			dargs = append(dargs, "--storage-driver=overlay")
    85  		} else {
    86  			log.Infof(c, "File system type: '%s' (%v)", out, err)
    87  		}
    88  	}
    89  
    90  	log.Infof(c, "Starting docker with %s", strings.Join(dargs, " "))
    91  	cmd := exec.Command("docker", dargs...)
    92  	if err := cmd.Start(); err != nil {
    93  		log.Errf(c, "Failed to start Docker. %s", err)
    94  		return -1, err
    95  	}
    96  	// Get the PID and return it.
    97  	return cmd.Process.Pid, nil
    98  }
    99  
   100  // WaitForStart delays until Docker appears to be up and running.
   101  //
   102  // Params:
   103  // 	- client (*docker.Client): Docker client.
   104  // 	- timeout (time.Duration): Time after which to give up.
   105  //
   106  // Returns:
   107  // 	- boolean true if the server is up.
   108  func WaitForStart(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   109  	if ok, missing := p.RequiresValue("client"); !ok {
   110  		return nil, &cookoo.FatalError{"Missing required fields: " + strings.Join(missing, ", ")}
   111  	}
   112  	cli := p.Get("client", nil).(*docli.Client)
   113  	timeout := p.Get("timeout", 30*time.Second).(time.Duration)
   114  
   115  	keepon := true
   116  	timer := time.AfterFunc(timeout, func() {
   117  		keepon = false
   118  	})
   119  
   120  	for keepon == true {
   121  		if err := cli.Ping(); err == nil {
   122  			timer.Stop()
   123  			log.Infof(c, "Docker is running.")
   124  			return true, nil
   125  		}
   126  		time.Sleep(time.Second)
   127  	}
   128  	return false, fmt.Errorf("Docker timed out after waiting %s for server.", timeout)
   129  }
   130  
   131  // BuildImg describes a build image.
   132  type BuildImg struct {
   133  	Path, Tag string
   134  }
   135  
   136  // ParallelBuild runs multiple docker builds at the same time.
   137  //
   138  // Params:
   139  //	-images ([]BuildImg): Images to build
   140  // 	-alwaysFetch (bool): Default false. If set to true, this will always fetch
   141  // 		the Docker image even if it already exists in the registry.
   142  //
   143  // Returns:
   144  //
   145  // 	- Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish.
   146  //
   147  // Context:
   148  //
   149  // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures
   150  // occurred during fetches.
   151  func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   152  	images := p.Get("images", []BuildImg{}).([]BuildImg)
   153  
   154  	var wg sync.WaitGroup
   155  	var m sync.Mutex
   156  	var fails int
   157  
   158  	for _, img := range images {
   159  		img := img
   160  		wg.Add(1)
   161  		safely.GoDo(c, func() {
   162  			log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag)
   163  			if _, err := buildImg(c, img.Path, img.Tag); err != nil {
   164  				log.Errf(c, "Failed to build docker image: %s", err)
   165  				m.Lock()
   166  				fails++
   167  				m.Unlock()
   168  			}
   169  			wg.Done()
   170  		})
   171  
   172  	}
   173  
   174  	// Number of failures.
   175  	c.Put("ParallelBuild.failN", fails)
   176  
   177  	return &wg, nil
   178  }
   179  
   180  // Waiter describes a thing that can wait.
   181  //
   182  // It does not bring you food. I should know. I tried.
   183  type Waiter interface {
   184  	Wait()
   185  }
   186  
   187  // Wait waits for a sync.WaitGroup to finish.
   188  //
   189  // Params:
   190  // 	- wg (Waiter): The thing to wait for.
   191  // 	- msg (string): The message to print when done. If this is empty, nothing is sent.
   192  // 	- waiting (string): String to tell what we're waiting for. If empty, nothing is displayed.
   193  // 	- failures (int): The number of failures that occurred while waiting.
   194  //
   195  // Returns:
   196  //  Nothing.
   197  func Wait(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   198  	ok, missing := p.RequiresValue("wg")
   199  	if !ok {
   200  		return nil, &cookoo.FatalError{"Missing required fields: " + strings.Join(missing, ", ")}
   201  	}
   202  	wg := p.Get("wg", nil).(Waiter)
   203  	msg := p.Get("msg", "").(string)
   204  	fails := p.Get("failures", 0).(int)
   205  	waitmsg := p.Get("waiting", "").(string)
   206  
   207  	if len(waitmsg) > 0 {
   208  		log.Info(c, waitmsg)
   209  	}
   210  
   211  	wg.Wait()
   212  	if len(msg) > 0 {
   213  		log.Info(c, msg)
   214  	}
   215  
   216  	if fails > 0 {
   217  		return nil, fmt.Errorf("There were %d failures while waiting.", fails)
   218  	}
   219  	return nil, nil
   220  }
   221  
   222  // BuildImage builds a docker image.
   223  //
   224  // Essentially, this executes:
   225  // 	docker build -t TAG PATH
   226  //
   227  // Params:
   228  // 	- path (string): The path to the image. REQUIRED
   229  // 	- tag (string): The tag to build.
   230  func BuildImage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   231  	path := p.Get("path", "").(string)
   232  	tag := p.Get("tag", "").(string)
   233  
   234  	log.Infof(c, "Building docker image %s (tag: %s)", path, tag)
   235  
   236  	return buildImg(c, path, tag)
   237  }
   238  
   239  func buildImg(c cookoo.Context, path, tag string) ([]byte, error) {
   240  	dargs := []string{"build"}
   241  	if len(tag) > 0 {
   242  		dargs = append(dargs, "-t", tag)
   243  	}
   244  
   245  	dargs = append(dargs, path)
   246  
   247  	out, err := exec.Command("docker", dargs...).CombinedOutput()
   248  	if len(out) > 0 {
   249  		log.Infof(c, "Docker: %s", out)
   250  	}
   251  	return out, err
   252  }
   253  
   254  // Push pushes an image to the registry.
   255  //
   256  // This finds the appropriate registry by looking it up in etcd.
   257  //
   258  // Params:
   259  // - client (etcd.Getter): Client to do etcd lookups.
   260  // - tag (string): Tag to push.
   261  //
   262  // Returns:
   263  //
   264  func Push(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   265  	// docker tag deis/slugrunner:lastest HOST:PORT/deis/slugrunner:latest
   266  	// docker push HOST:PORT/deis/slugrunner:latest
   267  	client := p.Get("client", nil).(etcd.Getter)
   268  
   269  	host, err := client.Get("/deis/registry/host", false, false)
   270  	if err != nil || host.Node == nil {
   271  		return nil, err
   272  	}
   273  	port, err := client.Get("/deis/registry/port", false, false)
   274  	if err != nil || host.Node == nil {
   275  		return nil, err
   276  	}
   277  
   278  	registry := host.Node.Value + ":" + port.Node.Value
   279  	tag := p.Get("tag", "").(string)
   280  
   281  	log.Infof(c, "Pushing %s to %s. This may take some time.", tag, registry)
   282  	rem := path.Join(registry, tag)
   283  
   284  	out, err := exec.Command("docker", "tag", "-f", tag, rem).CombinedOutput()
   285  	if err != nil {
   286  		log.Warnf(c, "Failed to tag %s on host %s: %s (%s)", tag, rem, err, out)
   287  	}
   288  	out, err = exec.Command("docker", "-D", "push", rem).CombinedOutput()
   289  
   290  	if err != nil {
   291  		log.Warnf(c, "Failed to push %s to host %s: %s (%s)", tag, rem, err, out)
   292  		return nil, err
   293  	}
   294  	log.Infof(c, "Finished pushing %s to %s.", tag, registry)
   295  	return nil, nil
   296  }
   297  
   298  /*
   299   * This function only works for very simple docker files that do not have
   300   * local resources.
   301   * Need to suck in all of the files in ADD directives, too.
   302   */
   303  // build takes a Dockerfile and builds an image.
   304  func build(c cookoo.Context, path, tag string, client *docli.Client) error {
   305  	dfile := filepath.Join(path, "Dockerfile")
   306  
   307  	// Stat the file
   308  	info, err := os.Stat(dfile)
   309  	if err != nil {
   310  		return fmt.Errorf("Dockerfile stat: %s", err)
   311  	}
   312  	file, err := os.Open(dfile)
   313  	if err != nil {
   314  		return fmt.Errorf("Dockerfile open: %s", err)
   315  	}
   316  	defer file.Close()
   317  
   318  	var buf bytes.Buffer
   319  	tw := tar.NewWriter(&buf)
   320  	tw.WriteHeader(&tar.Header{
   321  		Name:    "Dockerfile",
   322  		Size:    info.Size(),
   323  		ModTime: info.ModTime(),
   324  	})
   325  	io.Copy(tw, file)
   326  	if err := tw.Close(); err != nil {
   327  		return fmt.Errorf("Dockerfile tar: %s", err)
   328  	}
   329  
   330  	options := docli.BuildImageOptions{
   331  		Name:         tag,
   332  		InputStream:  &buf,
   333  		OutputStream: os.Stdout,
   334  	}
   335  	return client.BuildImage(options)
   336  }