github.com/chasestarr/deis@v1.13.5-0.20170519182049-1d9e59fbdbfc/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  		"--exec-opt",
    76  		"native.cgroupdriver=cgroupfs",
    77  	}
    78  
    79  	// For overlay-ish filesystems, force the overlay to kick in if it exists.
    80  	// Then we can check the fstype.
    81  	if err := os.MkdirAll("/", 0700); err == nil {
    82  
    83  		cmd := exec.Command("findmnt", "--noheadings", "--output", "FSTYPE", "--target", "/")
    84  
    85  		if out, err := cmd.Output(); err == nil && strings.TrimSpace(string(out)) == "overlay" {
    86  			dargs = append(dargs, "--storage-driver=overlay")
    87  		} else {
    88  			log.Infof(c, "File system type: '%s' (%v)", out, err)
    89  		}
    90  	}
    91  
    92  	log.Infof(c, "Starting docker with %s", strings.Join(dargs, " "))
    93  	cmd := exec.Command("docker", dargs...)
    94  	if err := cmd.Start(); err != nil {
    95  		log.Errf(c, "Failed to start Docker. %s", err)
    96  		return -1, err
    97  	}
    98  	// Get the PID and return it.
    99  	return cmd.Process.Pid, nil
   100  }
   101  
   102  // WaitForStart delays until Docker appears to be up and running.
   103  //
   104  // Params:
   105  // 	- client (*docker.Client): Docker client.
   106  // 	- timeout (time.Duration): Time after which to give up.
   107  //
   108  // Returns:
   109  // 	- boolean true if the server is up.
   110  func WaitForStart(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   111  	if ok, missing := p.RequiresValue("client"); !ok {
   112  		return nil, &cookoo.FatalError{"Missing required fields: " + strings.Join(missing, ", ")}
   113  	}
   114  	cli := p.Get("client", nil).(*docli.Client)
   115  	timeout := p.Get("timeout", 30*time.Second).(time.Duration)
   116  
   117  	keepon := true
   118  	timer := time.AfterFunc(timeout, func() {
   119  		keepon = false
   120  	})
   121  
   122  	for keepon == true {
   123  		if err := cli.Ping(); err == nil {
   124  			timer.Stop()
   125  			log.Infof(c, "Docker is running.")
   126  			return true, nil
   127  		}
   128  		time.Sleep(time.Second)
   129  	}
   130  	return false, fmt.Errorf("Docker timed out after waiting %s for server.", timeout)
   131  }
   132  
   133  // BuildImg describes a build image.
   134  type BuildImg struct {
   135  	Path, Tag string
   136  }
   137  
   138  // ParallelBuild runs multiple docker builds at the same time.
   139  //
   140  // Params:
   141  //	-images ([]BuildImg): Images to build
   142  // 	-alwaysFetch (bool): Default false. If set to true, this will always fetch
   143  // 		the Docker image even if it already exists in the registry.
   144  //
   145  // Returns:
   146  //
   147  // 	- Waiter: A *sync.WaitGroup that is waiting for the docker downloads to finish.
   148  //
   149  // Context:
   150  //
   151  // This puts 'ParallelBuild.failN" (int) into the context to indicate how many failures
   152  // occurred during fetches.
   153  func ParallelBuild(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   154  	images := p.Get("images", []BuildImg{}).([]BuildImg)
   155  
   156  	var wg sync.WaitGroup
   157  	var m sync.Mutex
   158  	var fails int
   159  
   160  	for _, img := range images {
   161  		img := img
   162  
   163  		// HACK: ensure "docker build" is serialized by allowing only one entry in
   164  		// the WaitGroup. This works around the "simultaneous docker pull" bug.
   165  		wg.Wait()
   166  		wg.Add(1)
   167  		safely.GoDo(c, func() {
   168  			log.Infof(c, "Starting build for %s (tag: %s)", img.Path, img.Tag)
   169  			if _, err := buildImg(c, img.Path, img.Tag); err != nil {
   170  				log.Errf(c, "Failed to build docker image: %s", err)
   171  				m.Lock()
   172  				fails++
   173  				m.Unlock()
   174  			}
   175  			wg.Done()
   176  		})
   177  
   178  	}
   179  
   180  	// Number of failures.
   181  	c.Put("ParallelBuild.failN", fails)
   182  
   183  	return &wg, nil
   184  }
   185  
   186  // Waiter describes a thing that can wait.
   187  //
   188  // It does not bring you food. I should know. I tried.
   189  type Waiter interface {
   190  	Wait()
   191  }
   192  
   193  // Wait waits for a sync.WaitGroup to finish.
   194  //
   195  // Params:
   196  // 	- wg (Waiter): The thing to wait for.
   197  // 	- msg (string): The message to print when done. If this is empty, nothing is sent.
   198  // 	- waiting (string): String to tell what we're waiting for. If empty, nothing is displayed.
   199  // 	- failures (int): The number of failures that occurred while waiting.
   200  //
   201  // Returns:
   202  //  Nothing.
   203  func Wait(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   204  	ok, missing := p.RequiresValue("wg")
   205  	if !ok {
   206  		return nil, &cookoo.FatalError{"Missing required fields: " + strings.Join(missing, ", ")}
   207  	}
   208  	wg := p.Get("wg", nil).(Waiter)
   209  	msg := p.Get("msg", "").(string)
   210  	fails := p.Get("failures", 0).(int)
   211  	waitmsg := p.Get("waiting", "").(string)
   212  
   213  	if len(waitmsg) > 0 {
   214  		log.Info(c, waitmsg)
   215  	}
   216  
   217  	wg.Wait()
   218  	if len(msg) > 0 {
   219  		log.Info(c, msg)
   220  	}
   221  
   222  	if fails > 0 {
   223  		return nil, fmt.Errorf("There were %d failures while waiting.", fails)
   224  	}
   225  	return nil, nil
   226  }
   227  
   228  // BuildImage builds a docker image.
   229  //
   230  // Essentially, this executes:
   231  // 	docker build -t TAG PATH
   232  //
   233  // Params:
   234  // 	- path (string): The path to the image. REQUIRED
   235  // 	- tag (string): The tag to build.
   236  func BuildImage(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   237  	path := p.Get("path", "").(string)
   238  	tag := p.Get("tag", "").(string)
   239  
   240  	log.Infof(c, "Building docker image %s (tag: %s)", path, tag)
   241  
   242  	return buildImg(c, path, tag)
   243  }
   244  
   245  func buildImg(c cookoo.Context, path, tag string) ([]byte, error) {
   246  	dargs := []string{"build"}
   247  	if len(tag) > 0 {
   248  		dargs = append(dargs, "-t", tag)
   249  	}
   250  
   251  	dargs = append(dargs, path)
   252  
   253  	out, err := exec.Command("docker", dargs...).CombinedOutput()
   254  	if len(out) > 0 {
   255  		log.Infof(c, "Docker: %s", out)
   256  	}
   257  	return out, err
   258  }
   259  
   260  // Push pushes an image to the registry.
   261  //
   262  // This finds the appropriate registry by looking it up in etcd.
   263  //
   264  // Params:
   265  // - client (etcd.Getter): Client to do etcd lookups.
   266  // - tag (string): Tag to push.
   267  //
   268  // Returns:
   269  //
   270  func Push(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) {
   271  	// docker tag deis/slugrunner:lastest HOST:PORT/deis/slugrunner:latest
   272  	// docker push HOST:PORT/deis/slugrunner:latest
   273  	client := p.Get("client", nil).(etcd.Getter)
   274  
   275  	host, err := client.Get("/deis/registry/host", false, false)
   276  	if err != nil || host.Node == nil {
   277  		return nil, err
   278  	}
   279  	port, err := client.Get("/deis/registry/port", false, false)
   280  	if err != nil || host.Node == nil {
   281  		return nil, err
   282  	}
   283  
   284  	registry := host.Node.Value + ":" + port.Node.Value
   285  	tag := p.Get("tag", "").(string)
   286  
   287  	log.Infof(c, "Pushing %s to %s. This may take some time.", tag, registry)
   288  	rem := path.Join(registry, tag)
   289  
   290  	out, err := exec.Command("docker", "tag", "-f", tag, rem).CombinedOutput()
   291  	if err != nil {
   292  		log.Warnf(c, "Failed to tag %s on host %s: %s (%s)", tag, rem, err, out)
   293  	}
   294  	out, err = exec.Command("docker", "-D", "push", rem).CombinedOutput()
   295  
   296  	if err != nil {
   297  		log.Warnf(c, "Failed to push %s to host %s: %s (%s)", tag, rem, err, out)
   298  		return nil, err
   299  	}
   300  	log.Infof(c, "Finished pushing %s to %s.", tag, registry)
   301  	return nil, nil
   302  }
   303  
   304  /*
   305   * This function only works for very simple docker files that do not have
   306   * local resources.
   307   * Need to suck in all of the files in ADD directives, too.
   308   */
   309  // build takes a Dockerfile and builds an image.
   310  func build(c cookoo.Context, path, tag string, client *docli.Client) error {
   311  	dfile := filepath.Join(path, "Dockerfile")
   312  
   313  	// Stat the file
   314  	info, err := os.Stat(dfile)
   315  	if err != nil {
   316  		return fmt.Errorf("Dockerfile stat: %s", err)
   317  	}
   318  	file, err := os.Open(dfile)
   319  	if err != nil {
   320  		return fmt.Errorf("Dockerfile open: %s", err)
   321  	}
   322  	defer file.Close()
   323  
   324  	var buf bytes.Buffer
   325  	tw := tar.NewWriter(&buf)
   326  	tw.WriteHeader(&tar.Header{
   327  		Name:    "Dockerfile",
   328  		Size:    info.Size(),
   329  		ModTime: info.ModTime(),
   330  	})
   331  	io.Copy(tw, file)
   332  	if err := tw.Close(); err != nil {
   333  		return fmt.Errorf("Dockerfile tar: %s", err)
   334  	}
   335  
   336  	options := docli.BuildImageOptions{
   337  		Name:         tag,
   338  		InputStream:  &buf,
   339  		OutputStream: os.Stdout,
   340  	}
   341  	return client.BuildImage(options)
   342  }