Merge pull request #511 from dotcloud/builder-cache

+ Builder: Add caching to docker builder
This commit is contained in:
Guillaume J. Charmes 2013-05-06 17:20:22 -07:00
commit d581f0808c
2 changed files with 94 additions and 10 deletions

View File

@ -165,6 +165,35 @@ func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
} }
} }
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) { func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
var ( var (
image, base *Image image, base *Image
@ -183,7 +212,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
} }
return nil, err return nil, err
} }
line = strings.TrimSpace(line) line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line // Skip comments and empty line
if len(line) == 0 || line[0] == '#' { if len(line) == 0 || line[0] == '#' {
continue continue
@ -202,10 +231,12 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
if builder.runtime.graph.IsNotExist(err) { if builder.runtime.graph.IsNotExist(err) {
var tag, remote string var tag, remote string
if strings.Contains(remote, ":") { if strings.Contains(arguments, ":") {
remoteParts := strings.Split(remote, ":") remoteParts := strings.Split(arguments, ":")
tag = remoteParts[1] tag = remoteParts[1]
remote = remoteParts[0] remote = remoteParts[0]
} else {
remote = arguments
} }
if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil { if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
@ -216,7 +247,6 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
return nil, err return nil, err
} }
@ -237,6 +267,14 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
return nil, err return nil, 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 // Create the container and start it
c, err := builder.Create(config) c, err := builder.Create(config)
if err != nil { if err != nil {
@ -344,10 +382,10 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
break break
default: default:
fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", instruction) fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
} }
} }
if base != nil { if image != nil {
// The build is successful, keep the temporary containers and images // The build is successful, keep the temporary containers and images
for i := range tmpImages { for i := range tmpImages {
delete(tmpImages, i) delete(tmpImages, i)
@ -355,9 +393,8 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
for i := range tmpContainers { for i := range tmpContainers {
delete(tmpContainers, i) delete(tmpContainers, i)
} }
fmt.Fprintf(stdout, "Build finished. image id: %s\n", base.ShortId()) fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
} else { return image, nil
fmt.Fprintf(stdout, "An error occured during the build\n")
} }
return base, nil return nil, fmt.Errorf("An error occured during the build\n")
} }

View File

@ -474,3 +474,50 @@ func FindCgroupMountpoint(cgroupType string) (string, error) {
return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) 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
}