Browse Source

Implement caching for docker builder

Guillaume J. Charmes 12 years ago
parent
commit
a46fc3a59e
2 changed files with 115 additions and 7 deletions
  1. 68 7
      builder.go
  2. 47 0
      utils.go

+ 68 - 7
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")
 }

+ 47 - 0
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
+}