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 }