Browse code

Implement caching for docker builder

Guillaume J. Charmes authored on 2013/05/02 16:49:23
Showing 2 changed files
... ...
@@ -145,8 +145,55 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
145 145
 	return img, nil
146 146
 }
147 147
 
148
-func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error {
149
-	var image, base *Image
148
+func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
149
+	for c := range containers {
150
+		tmp := builder.runtime.Get(c)
151
+		builder.runtime.Destroy(tmp)
152
+		Debugf("Removing container %s", c)
153
+	}
154
+	for i := range images {
155
+		builder.runtime.graph.Delete(i)
156
+		Debugf("Removing image %s", i)
157
+	}
158
+}
159
+
160
+func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
161
+	// Retrieve all images
162
+	images, err := builder.graph.All()
163
+	if err != nil {
164
+		return nil, err
165
+	}
166
+
167
+	// Store the tree in a map of map (map[parentId][childId])
168
+	imageMap := make(map[string]map[string]struct{})
169
+	for _, img := range images {
170
+		if _, exists := imageMap[img.Parent]; !exists {
171
+			imageMap[img.Parent] = make(map[string]struct{})
172
+		}
173
+		imageMap[img.Parent][img.Id] = struct{}{}
174
+	}
175
+
176
+	// Loop on the children of the given image and check the config
177
+	for elem := range imageMap[image.Id] {
178
+		img, err := builder.graph.Get(elem)
179
+		if err != nil {
180
+			return nil, err
181
+		}
182
+		if CompareConfig(&img.ContainerConfig, config) {
183
+			return img, nil
184
+		}
185
+	}
186
+	return nil, nil
187
+}
188
+
189
+func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
190
+	var (
191
+		image, base   *Image
192
+		maintainer    string
193
+		tmpContainers map[string]struct{} = make(map[string]struct{})
194
+		tmpImages     map[string]struct{} = make(map[string]struct{})
195
+	)
196
+	defer builder.clearTmp(tmpContainers, tmpImages)
150 197
 
151 198
 	file := bufio.NewReader(dockerfile)
152 199
 	for {
... ...
@@ -204,6 +251,14 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error {
204 204
 				return err
205 205
 			}
206 206
 
207
+			if cache, err := builder.getCachedImage(image, config); err != nil {
208
+				return nil, err
209
+			} else if cache != nil {
210
+				image = cache
211
+				fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
212
+				break
213
+			}
214
+
207 215
 			// Create the container and start it
208 216
 			c, err := builder.Create(config)
209 217
 			if err != nil {
... ...
@@ -276,10 +331,16 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) error {
276 276
 			fmt.Fprintf(stdout, "Skipping unknown op %s\n", tmp[0])
277 277
 		}
278 278
 	}
279
-	if base != nil {
280
-		fmt.Fprintf(stdout, "Build finished. image id: %s\n", base.Id)
281
-	} else {
282
-		fmt.Fprintf(stdout, "An error occured during the build\n")
279
+	if image != nil {
280
+		// The build is successful, keep the temporary containers and images
281
+		for i := range tmpImages {
282
+			delete(tmpImages, i)
283
+		}
284
+		for i := range tmpContainers {
285
+			delete(tmpContainers, i)
286
+		}
287
+		fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
288
+		return image, nil
283 289
 	}
284
-	return nil
290
+	return nil, fmt.Errorf("An error occured during the build\n")
285 291
 }
... ...
@@ -474,3 +474,50 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
474 474
 
475 475
 	return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
476 476
 }
477
+
478
+// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
479
+// If OpenStdin is set, then it differs
480
+func CompareConfig(a, b *Config) bool {
481
+	if a == nil || b == nil ||
482
+		a.OpenStdin || b.OpenStdin {
483
+		return false
484
+	}
485
+	if a.AttachStdout != b.AttachStdout ||
486
+		a.AttachStderr != b.AttachStderr ||
487
+		a.User != b.User ||
488
+		a.Memory != b.Memory ||
489
+		a.MemorySwap != b.MemorySwap ||
490
+		a.OpenStdin != b.OpenStdin ||
491
+		a.Tty != b.Tty {
492
+		return false
493
+	}
494
+	if len(a.Cmd) != len(b.Cmd) ||
495
+		len(a.Dns) != len(b.Dns) ||
496
+		len(a.Env) != len(b.Env) ||
497
+		len(a.PortSpecs) != len(b.PortSpecs) {
498
+		return false
499
+	}
500
+
501
+	for i := 0; i < len(a.Cmd); i++ {
502
+		if a.Cmd[i] != b.Cmd[i] {
503
+			return false
504
+		}
505
+	}
506
+	for i := 0; i < len(a.Dns); i++ {
507
+		if a.Dns[i] != b.Dns[i] {
508
+			return false
509
+		}
510
+	}
511
+	for i := 0; i < len(a.Env); i++ {
512
+		if a.Env[i] != b.Env[i] {
513
+			return false
514
+		}
515
+	}
516
+	for i := 0; i < len(a.PortSpecs); i++ {
517
+		if a.PortSpecs[i] != b.PortSpecs[i] {
518
+			return false
519
+		}
520
+	}
521
+
522
+	return true
523
+}