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 }