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 }