github.com/jiasir/deis@v1.12.2/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  
   161  		// HACK: ensure "docker build" is serialized by allowing only one entry in
   162  		// the WaitGroup. This works around the "simultaneous docker pull" bug.
   163  		wg.Wait()
   164  		wg.Add(1)
   165  		safely.GoDo(c, func() {
   166  			log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag)
   167  			if _, err := buildImg(c, img.Path, img.Tag); err != nil {
   168  				log.Errf(c, "Failed to build docker image: %s", err)
   169  				m.Lock()
   170  				fails++
   171  				m.Unlock()
   172  			}
   173  			wg.Done()
   174  		})
   175  
   176  	}
   177  
   178  	// Number of failures.
   179  	c.Put("ParallelBuild.failN", fails)
   180  
   181  	return &wg, nil
   182  }
   183  
   184  // Waiter describes a thing that can wait.
   185  //
   186  // It does not bring you food. I should know. I tried.
   187  type Waiter interface {
   188  	Wait()
   189  }
   190  
   191  // Wait waits for a sync.WaitGroup to finish.
   192  //
   193  // Params:
   194  // 	- wg (Waiter): The thing to wait for.
   195  // 	- msg (string): The message to print when done. If this is empty, nothing is sent.
   196  // 	- waiting (string): String to tell what we're waiting for. If empty, nothing is displayed.
   197  // 	- failures (int): The number of failures that occurred while waiting.
   198  //
   199  // Returns:
   200  //  Nothing.
   201  func Wait(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   202  	ok, missing := p.RequiresValue("wg")
   203  	if !ok {
   204  		return nil, &cookoo.FatalError{"Missing required fields: " + strings.Join(missing, ", ")}
   205  	}
   206  	wg := p.Get("wg", nil).(Waiter)
   207  	msg := p.Get("msg", "").(string)
   208  	fails := p.Get("failures", 0).(int)
   209  	waitmsg := p.Get("waiting", "").(string)
   210  
   211  	if len(waitmsg) > 0 {
   212  		log.Info(c, waitmsg)
   213  	}
   214  
   215  	wg.Wait()
   216  	if len(msg) > 0 {
   217  		log.Info(c, msg)
   218  	}
   219  
   220  	if fails > 0 {
   221  		return nil, fmt.Errorf("There were %d failures while waiting.", fails)
   222  	}
   223  	return nil, nil
   224  }
   225  
   226  // BuildImage builds a docker image.
   227  //
   228  // Essentially, this executes:
   229  // 	docker build -t TAG PATH
   230  //
   231  // Params:
   232  // 	- path (string): The path to the image. REQUIRED
   233  // 	- tag (string): The tag to build.
   234  func BuildImage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   235  	path := p.Get("path", "").(string)
   236  	tag := p.Get("tag", "").(string)
   237  
   238  	log.Infof(c, "Building docker image %s (tag: %s)", path, tag)
   239  
   240  	return buildImg(c, path, tag)
   241  }
   242  
   243  func buildImg(c cookoo.Context, path, tag string) ([]byte, error) {
   244  	dargs := []string{"build"}
   245  	if len(tag) > 0 {
   246  		dargs = append(dargs, "-t", tag)
   247  	}
   248  
   249  	dargs = append(dargs, path)
   250  
   251  	out, err := exec.Command("docker", dargs...).CombinedOutput()
   252  	if len(out) > 0 {
   253  		log.Infof(c, "Docker: %s", out)
   254  	}
   255  	return out, err
   256  }
   257  
   258  // Push pushes an image to the registry.
   259  //
   260  // This finds the appropriate registry by looking it up in etcd.
   261  //
   262  // Params:
   263  // - client (etcd.Getter): Client to do etcd lookups.
   264  // - tag (string): Tag to push.
   265  //
   266  // Returns:
   267  //
   268  func Push(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   269  	// docker tag deis/slugrunner:lastest HOST:PORT/deis/slugrunner:latest
   270  	// docker push HOST:PORT/deis/slugrunner:latest
   271  	client := p.Get("client", nil).(etcd.Getter)
   272  
   273  	host, err := client.Get("/deis/registry/host", false, false)
   274  	if err != nil || host.Node == nil {
   275  		return nil, err
   276  	}
   277  	port, err := client.Get("/deis/registry/port", false, false)
   278  	if err != nil || host.Node == nil {
   279  		return nil, err
   280  	}
   281  
   282  	registry := host.Node.Value + ":" + port.Node.Value
   283  	tag := p.Get("tag", "").(string)
   284  
   285  	log.Infof(c, "Pushing %s to %s. This may take some time.", tag, registry)
   286  	rem := path.Join(registry, tag)
   287  
   288  	out, err := exec.Command("docker", "tag", "-f", tag, rem).CombinedOutput()
   289  	if err != nil {
   290  		log.Warnf(c, "Failed to tag %s on host %s: %s (%s)", tag, rem, err, out)
   291  	}
   292  	out, err = exec.Command("docker", "-D", "push", rem).CombinedOutput()
   293  
   294  	if err != nil {
   295  		log.Warnf(c, "Failed to push %s to host %s: %s (%s)", tag, rem, err, out)
   296  		return nil, err
   297  	}
   298  	log.Infof(c, "Finished pushing %s to %s.", tag, registry)
   299  	return nil, nil
   300  }
   301  
   302  /*
   303   * This function only works for very simple docker files that do not have
   304   * local resources.
   305   * Need to suck in all of the files in ADD directives, too.
   306   */
   307  // build takes a Dockerfile and builds an image.
   308  func build(c cookoo.Context, path, tag string, client *docli.Client) error {
   309  	dfile := filepath.Join(path, "Dockerfile")
   310  
   311  	// Stat the file
   312  	info, err := os.Stat(dfile)
   313  	if err != nil {
   314  		return fmt.Errorf("Dockerfile stat: %s", err)
   315  	}
   316  	file, err := os.Open(dfile)
   317  	if err != nil {
   318  		return fmt.Errorf("Dockerfile open: %s", err)
   319  	}
   320  	defer file.Close()
   321  
   322  	var buf bytes.Buffer
   323  	tw := tar.NewWriter(&buf)
   324  	tw.WriteHeader(&tar.Header{
   325  		Name:    "Dockerfile",
   326  		Size:    info.Size(),
   327  		ModTime: info.ModTime(),
   328  	})
   329  	io.Copy(tw, file)
   330  	if err := tw.Close(); err != nil {
   331  		return fmt.Errorf("Dockerfile tar: %s", err)
   332  	}
   333  
   334  	options := docli.BuildImageOptions{
   335  		Name:         tag,
   336  		InputStream:  &buf,
   337  		OutputStream: os.Stdout,
   338  	}
   339  	return client.BuildImage(options)
   340  }