From a46fc3a59e585e005ec67b05211ca9de02f0c4ff Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 2 May 2013 00:49:23 -0700 Subject: [PATCH] Implement caching for docker builder --- builder.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++----- utils.go | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/builder.go b/builder.go index 5ac4fa3c36..e7f5e330f7 100644 --- a/builder.go +++ b/builder.go @@ -145,8 +145,55 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a return img, nil } -func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error { - var image, base *Image +func (builder *Builder) clearTmp(containers, images map[string]struct{}) { + for c := range containers { + tmp := builder.runtime.Get(c) + builder.runtime.Destroy(tmp) + Debugf("Removing container %s", c) + } + for i := range images { + builder.runtime.graph.Delete(i) + Debugf("Removing image %s", i) + } +} + +func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) { + // Retrieve all images + images, err := builder.graph.All() + if err != nil { + return nil, err + } + + // Store the tree in a map of map (map[parentId][childId]) + imageMap := make(map[string]map[string]struct{}) + for _, img := range images { + if _, exists := imageMap[img.Parent]; !exists { + imageMap[img.Parent] = make(map[string]struct{}) + } + imageMap[img.Parent][img.Id] = struct{}{} + } + + // Loop on the children of the given image and check the config + for elem := range imageMap[image.Id] { + img, err := builder.graph.Get(elem) + if err != nil { + return nil, err + } + if CompareConfig(&img.ContainerConfig, config) { + return img, nil + } + } + return nil, nil +} + +func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) { + var ( + image, base *Image + maintainer string + tmpContainers map[string]struct{} = make(map[string]struct{}) + tmpImages map[string]struct{} = make(map[string]struct{}) + ) + defer builder.clearTmp(tmpContainers, tmpImages) file := bufio.NewReader(dockerfile) for { @@ -204,6 +251,14 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error { return err } + if cache, err := builder.getCachedImage(image, config); err != nil { + return nil, err + } else if cache != nil { + image = cache + fmt.Fprintf(stdout, "===> %s\n", image.ShortId()) + break + } + // Create the container and start it c, err := builder.Create(config) if err != nil { @@ -276,10 +331,16 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error { fmt.Fprintf(stdout, "Skipping unknown op %s\n", tmp[0]) } } - if base != nil { - fmt.Fprintf(stdout, "Build finished. image id: %s\n", base.Id) - } else { - fmt.Fprintf(stdout, "An error occured during the build\n") + if image != nil { + // The build is successful, keep the temporary containers and images + for i := range tmpImages { + delete(tmpImages, i) + } + for i := range tmpContainers { + delete(tmpContainers, i) + } + fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId()) + return image, nil } - return nil + return nil, fmt.Errorf("An error occured during the build\n") } diff --git a/utils.go b/utils.go index 047a29abef..095be2f4bf 100644 --- a/utils.go +++ b/utils.go @@ -474,3 +474,50 @@ func FindCgroupMountpoint(cgroupType string) (string, error) { return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) } + +// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields +// If OpenStdin is set, then it differs +func CompareConfig(a, b *Config) bool { + if a == nil || b == nil || + a.OpenStdin || b.OpenStdin { + return false + } + if a.AttachStdout != b.AttachStdout || + a.AttachStderr != b.AttachStderr || + a.User != b.User || + a.Memory != b.Memory || + a.MemorySwap != b.MemorySwap || + a.OpenStdin != b.OpenStdin || + a.Tty != b.Tty { + return false + } + if len(a.Cmd) != len(b.Cmd) || + len(a.Dns) != len(b.Dns) || + len(a.Env) != len(b.Env) || + len(a.PortSpecs) != len(b.PortSpecs) { + return false + } + + for i := 0; i < len(a.Cmd); i++ { + if a.Cmd[i] != b.Cmd[i] { + return false + } + } + for i := 0; i < len(a.Dns); i++ { + if a.Dns[i] != b.Dns[i] { + return false + } + } + for i := 0; i < len(a.Env); i++ { + if a.Env[i] != b.Env[i] { + return false + } + } + for i := 0; i < len(a.PortSpecs); i++ { + if a.PortSpecs[i] != b.PortSpecs[i] { + return false + } + } + + return true +}