Based on work by KJ Tsanaktsidis
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: KJ Tsanaktsidis <kjtsanaktsidis@gmail.com>
| ... | ... |
@@ -70,7 +70,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui |
| 70 | 70 |
var buildUlimits = []*units.Ulimit{}
|
| 71 | 71 |
ulimitsJSON := r.FormValue("ulimits")
|
| 72 | 72 |
if ulimitsJSON != "" {
|
| 73 |
- if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
|
| 73 |
+ if err := json.Unmarshal([]byte(ulimitsJSON), &buildUlimits); err != nil {
|
|
| 74 | 74 |
return nil, err |
| 75 | 75 |
} |
| 76 | 76 |
options.Ulimits = buildUlimits |
| ... | ... |
@@ -79,7 +79,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui |
| 79 | 79 |
var buildArgs = map[string]string{}
|
| 80 | 80 |
buildArgsJSON := r.FormValue("buildargs")
|
| 81 | 81 |
if buildArgsJSON != "" {
|
| 82 |
- if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
|
| 82 |
+ if err := json.Unmarshal([]byte(buildArgsJSON), &buildArgs); err != nil {
|
|
| 83 | 83 |
return nil, err |
| 84 | 84 |
} |
| 85 | 85 |
options.BuildArgs = buildArgs |
| ... | ... |
@@ -87,12 +87,21 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui |
| 87 | 87 |
var labels = map[string]string{}
|
| 88 | 88 |
labelsJSON := r.FormValue("labels")
|
| 89 | 89 |
if labelsJSON != "" {
|
| 90 |
- if err := json.NewDecoder(strings.NewReader(labelsJSON)).Decode(&labels); err != nil {
|
|
| 90 |
+ if err := json.Unmarshal([]byte(labelsJSON), &labels); err != nil {
|
|
| 91 | 91 |
return nil, err |
| 92 | 92 |
} |
| 93 | 93 |
options.Labels = labels |
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 |
+ var cacheFrom = []string{}
|
|
| 97 |
+ cacheFromJSON := r.FormValue("cachefrom")
|
|
| 98 |
+ if cacheFromJSON != "" {
|
|
| 99 |
+ if err := json.Unmarshal([]byte(cacheFromJSON), &cacheFrom); err != nil {
|
|
| 100 |
+ return nil, err |
|
| 101 |
+ } |
|
| 102 |
+ options.CacheFrom = cacheFrom |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 96 | 105 |
return options, nil |
| 97 | 106 |
} |
| 98 | 107 |
|
| ... | ... |
@@ -151,6 +151,9 @@ type ImageBuildOptions struct {
|
| 151 | 151 |
// preserves the original image and creates a new one from the parent with all |
| 152 | 152 |
// the changes applied to a single layer |
| 153 | 153 |
Squash bool |
| 154 |
+ // CacheFrom specifies images that are used for matching cache. Images |
|
| 155 |
+ // specified here do not need to have a valid parent chain to match cache. |
|
| 156 |
+ CacheFrom []string |
|
| 154 | 157 |
} |
| 155 | 158 |
|
| 156 | 159 |
// ImageBuildResponse holds information |
| ... | ... |
@@ -153,10 +153,16 @@ type Image interface {
|
| 153 | 153 |
RunConfig() *container.Config |
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 |
-// ImageCache abstracts an image cache store. |
|
| 156 |
+// ImageCacheBuilder represents a generator for stateful image cache. |
|
| 157 |
+type ImageCacheBuilder interface {
|
|
| 158 |
+ // MakeImageCache creates a stateful image cache. |
|
| 159 |
+ MakeImageCache(cacheFrom []string) ImageCache |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+// ImageCache abstracts an image cache. |
|
| 157 | 163 |
// (parent image, child runconfig) -> child image |
| 158 | 164 |
type ImageCache interface {
|
| 159 | 165 |
// GetCachedImageOnBuild returns a reference to a cached image whose parent equals `parent` |
| 160 | 166 |
// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error. |
| 161 |
- GetCachedImageOnBuild(parentID string, cfg *container.Config) (imageID string, err error) |
|
| 167 |
+ GetCache(parentID string, cfg *container.Config) (imageID string, err error) |
|
| 162 | 168 |
} |
| ... | ... |
@@ -75,6 +75,8 @@ type Builder struct {
|
| 75 | 75 |
|
| 76 | 76 |
// TODO: remove once docker.Commit can receive a tag |
| 77 | 77 |
id string |
| 78 |
+ |
|
| 79 |
+ imageCache builder.ImageCache |
|
| 78 | 80 |
} |
| 79 | 81 |
|
| 80 | 82 |
// BuildManager implements builder.Backend and is shared across all Builder objects. |
| ... | ... |
@@ -136,6 +138,10 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back |
| 136 | 136 |
LookingForDirectives: true, |
| 137 | 137 |
}, |
| 138 | 138 |
} |
| 139 |
+ if icb, ok := backend.(builder.ImageCacheBuilder); ok {
|
|
| 140 |
+ b.imageCache = icb.MakeImageCache(config.CacheFrom) |
|
| 141 |
+ } |
|
| 142 |
+ |
|
| 139 | 143 |
parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape |
| 140 | 144 |
|
| 141 | 145 |
if dockerfile != nil {
|
| ... | ... |
@@ -438,18 +438,16 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
| 438 | 438 |
return nil |
| 439 | 439 |
} |
| 440 | 440 |
|
| 441 |
-// probeCache checks if `b.docker` implements builder.ImageCache and image-caching |
|
| 442 |
-// is enabled (`b.UseCache`). |
|
| 443 |
-// If so attempts to look up the current `b.image` and `b.runConfig` pair with `b.docker`. |
|
| 441 |
+// probeCache checks if cache match can be found for current build instruction. |
|
| 444 | 442 |
// If an image is found, probeCache returns `(true, nil)`. |
| 445 | 443 |
// If no image is found, it returns `(false, nil)`. |
| 446 | 444 |
// If there is any error, it returns `(false, err)`. |
| 447 | 445 |
func (b *Builder) probeCache() (bool, error) {
|
| 448 |
- c, ok := b.docker.(builder.ImageCache) |
|
| 449 |
- if !ok || b.options.NoCache || b.cacheBusted {
|
|
| 446 |
+ c := b.imageCache |
|
| 447 |
+ if c == nil || b.options.NoCache || b.cacheBusted {
|
|
| 450 | 448 |
return false, nil |
| 451 | 449 |
} |
| 452 |
- cache, err := c.GetCachedImageOnBuild(b.image, b.runConfig) |
|
| 450 |
+ cache, err := c.GetCache(b.image, b.runConfig) |
|
| 453 | 451 |
if err != nil {
|
| 454 | 452 |
return false, err |
| 455 | 453 |
} |
| ... | ... |
@@ -55,6 +55,7 @@ type buildOptions struct {
|
| 55 | 55 |
rm bool |
| 56 | 56 |
forceRm bool |
| 57 | 57 |
pull bool |
| 58 |
+ cacheFrom []string |
|
| 58 | 59 |
} |
| 59 | 60 |
|
| 60 | 61 |
// NewBuildCommand creates a new `docker build` command |
| ... | ... |
@@ -98,6 +99,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
| 98 | 98 |
flags.BoolVar(&options.forceRm, "force-rm", false, "Always remove intermediate containers") |
| 99 | 99 |
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success") |
| 100 | 100 |
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image") |
| 101 |
+ flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
|
|
| 101 | 102 |
|
| 102 | 103 |
command.AddTrustedFlags(flags, true) |
| 103 | 104 |
|
| ... | ... |
@@ -289,6 +291,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
| 289 | 289 |
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()), |
| 290 | 290 |
AuthConfigs: authConfig, |
| 291 | 291 |
Labels: runconfigopts.ConvertKVStringsToMap(options.labels), |
| 292 |
+ CacheFrom: options.cacheFrom, |
|
| 292 | 293 |
} |
| 293 | 294 |
|
| 294 | 295 |
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions) |
| ... | ... |
@@ -110,6 +110,13 @@ func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, erro |
| 110 | 110 |
return query, err |
| 111 | 111 |
} |
| 112 | 112 |
query.Set("labels", string(labelsJSON))
|
| 113 |
+ |
|
| 114 |
+ cacheFromJSON, err := json.Marshal(options.CacheFrom) |
|
| 115 |
+ if err != nil {
|
|
| 116 |
+ return query, err |
|
| 117 |
+ } |
|
| 118 |
+ query.Set("cachefrom", string(cacheFromJSON))
|
|
| 119 |
+ |
|
| 113 | 120 |
return query, nil |
| 114 | 121 |
} |
| 115 | 122 |
|
| 116 | 123 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,254 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "reflect" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/Sirupsen/logrus" |
|
| 9 |
+ containertypes "github.com/docker/docker/api/types/container" |
|
| 10 |
+ "github.com/docker/docker/builder" |
|
| 11 |
+ "github.com/docker/docker/dockerversion" |
|
| 12 |
+ "github.com/docker/docker/image" |
|
| 13 |
+ "github.com/docker/docker/layer" |
|
| 14 |
+ "github.com/docker/docker/runconfig" |
|
| 15 |
+ "github.com/pkg/errors" |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+// getLocalCachedImage returns the most recent created image that is a child |
|
| 19 |
+// of the image with imgID, that had the same config when it was |
|
| 20 |
+// created. nil is returned if a child cannot be found. An error is |
|
| 21 |
+// returned if the parent image cannot be found. |
|
| 22 |
+func (daemon *Daemon) getLocalCachedImage(imgID image.ID, config *containertypes.Config) (*image.Image, error) {
|
|
| 23 |
+ // Loop on the children of the given image and check the config |
|
| 24 |
+ getMatch := func(siblings []image.ID) (*image.Image, error) {
|
|
| 25 |
+ var match *image.Image |
|
| 26 |
+ for _, id := range siblings {
|
|
| 27 |
+ img, err := daemon.imageStore.Get(id) |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return nil, fmt.Errorf("unable to find image %q", id)
|
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ if runconfig.Compare(&img.ContainerConfig, config) {
|
|
| 33 |
+ // check for the most up to date match |
|
| 34 |
+ if match == nil || match.Created.Before(img.Created) {
|
|
| 35 |
+ match = img |
|
| 36 |
+ } |
|
| 37 |
+ } |
|
| 38 |
+ } |
|
| 39 |
+ return match, nil |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ // In this case, this is `FROM scratch`, which isn't an actual image. |
|
| 43 |
+ if imgID == "" {
|
|
| 44 |
+ images := daemon.imageStore.Map() |
|
| 45 |
+ var siblings []image.ID |
|
| 46 |
+ for id, img := range images {
|
|
| 47 |
+ if img.Parent == imgID {
|
|
| 48 |
+ siblings = append(siblings, id) |
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ return getMatch(siblings) |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ // find match from child images |
|
| 55 |
+ siblings := daemon.imageStore.Children(imgID) |
|
| 56 |
+ return getMatch(siblings) |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+// MakeImageCache creates a stateful image cache. |
|
| 60 |
+func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
|
|
| 61 |
+ if len(sourceRefs) == 0 {
|
|
| 62 |
+ return &localImageCache{daemon}
|
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ cache := &imageCache{daemon: daemon, localImageCache: &localImageCache{daemon}}
|
|
| 66 |
+ |
|
| 67 |
+ for _, ref := range sourceRefs {
|
|
| 68 |
+ img, err := daemon.GetImage(ref) |
|
| 69 |
+ if err != nil {
|
|
| 70 |
+ logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
|
|
| 71 |
+ continue |
|
| 72 |
+ } |
|
| 73 |
+ cache.sources = append(cache.sources, img) |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ return cache |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+// localImageCache is cache based on parent chain. |
|
| 80 |
+type localImageCache struct {
|
|
| 81 |
+ daemon *Daemon |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func (lic *localImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
|
|
| 85 |
+ return getImageIDAndError(lic.daemon.getLocalCachedImage(image.ID(imgID), config)) |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+// imageCache is cache based on history objects. Requires initial set of images. |
|
| 89 |
+type imageCache struct {
|
|
| 90 |
+ sources []*image.Image |
|
| 91 |
+ daemon *Daemon |
|
| 92 |
+ localImageCache *localImageCache |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func (ic *imageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) {
|
|
| 96 |
+ var history []image.History |
|
| 97 |
+ rootFS := image.NewRootFS() |
|
| 98 |
+ lenHistory := 0 |
|
| 99 |
+ if parent != nil {
|
|
| 100 |
+ history = parent.History |
|
| 101 |
+ rootFS = parent.RootFS |
|
| 102 |
+ lenHistory = len(parent.History) |
|
| 103 |
+ } |
|
| 104 |
+ history = append(history, target.History[lenHistory]) |
|
| 105 |
+ if layer := getLayerForHistoryIndex(target, lenHistory); layer != "" {
|
|
| 106 |
+ rootFS.Append(layer) |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ config, err := json.Marshal(&image.Image{
|
|
| 110 |
+ V1Image: image.V1Image{
|
|
| 111 |
+ DockerVersion: dockerversion.Version, |
|
| 112 |
+ Config: cfg, |
|
| 113 |
+ Architecture: target.Architecture, |
|
| 114 |
+ OS: target.OS, |
|
| 115 |
+ Author: target.Author, |
|
| 116 |
+ Created: history[len(history)-1].Created, |
|
| 117 |
+ }, |
|
| 118 |
+ RootFS: rootFS, |
|
| 119 |
+ History: history, |
|
| 120 |
+ OSFeatures: target.OSFeatures, |
|
| 121 |
+ OSVersion: target.OSVersion, |
|
| 122 |
+ }) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return "", errors.Wrap(err, "failed to marshal image config") |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ imgID, err := ic.daemon.imageStore.Create(config) |
|
| 128 |
+ if err != nil {
|
|
| 129 |
+ return "", errors.Wrap(err, "failed to create cache image") |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ if parent != nil {
|
|
| 133 |
+ if err := ic.daemon.imageStore.SetParent(imgID, parent.ID()); err != nil {
|
|
| 134 |
+ return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID()) |
|
| 135 |
+ } |
|
| 136 |
+ } |
|
| 137 |
+ return imgID, nil |
|
| 138 |
+} |
|
| 139 |
+ |
|
| 140 |
+func (ic *imageCache) isParent(imgID, parentID image.ID) bool {
|
|
| 141 |
+ nextParent, err := ic.daemon.imageStore.GetParent(imgID) |
|
| 142 |
+ if err != nil {
|
|
| 143 |
+ return false |
|
| 144 |
+ } |
|
| 145 |
+ if nextParent == parentID {
|
|
| 146 |
+ return true |
|
| 147 |
+ } |
|
| 148 |
+ return ic.isParent(nextParent, parentID) |
|
| 149 |
+} |
|
| 150 |
+ |
|
| 151 |
+func (ic *imageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
|
|
| 152 |
+ imgID, err := ic.localImageCache.GetCache(parentID, cfg) |
|
| 153 |
+ if err != nil {
|
|
| 154 |
+ return "", err |
|
| 155 |
+ } |
|
| 156 |
+ if imgID != "" {
|
|
| 157 |
+ for _, s := range ic.sources {
|
|
| 158 |
+ if ic.isParent(s.ID(), image.ID(imgID)) {
|
|
| 159 |
+ return imgID, nil |
|
| 160 |
+ } |
|
| 161 |
+ } |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ var parent *image.Image |
|
| 165 |
+ lenHistory := 0 |
|
| 166 |
+ if parentID != "" {
|
|
| 167 |
+ parent, err = ic.daemon.imageStore.Get(image.ID(parentID)) |
|
| 168 |
+ if err != nil {
|
|
| 169 |
+ return "", errors.Wrapf(err, "unable to find image %v", parentID) |
|
| 170 |
+ } |
|
| 171 |
+ lenHistory = len(parent.History) |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ for _, target := range ic.sources {
|
|
| 175 |
+ if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
|
|
| 176 |
+ continue |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ if len(target.History)-1 == lenHistory { // last
|
|
| 180 |
+ if parent != nil {
|
|
| 181 |
+ if err := ic.daemon.imageStore.SetParent(target.ID(), parent.ID()); err != nil {
|
|
| 182 |
+ return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID()) |
|
| 183 |
+ } |
|
| 184 |
+ } |
|
| 185 |
+ return target.ID().String(), nil |
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ imgID, err := ic.restoreCachedImage(parent, target, cfg) |
|
| 189 |
+ if err != nil {
|
|
| 190 |
+ return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID()) |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm
|
|
| 194 |
+ return imgID.String(), nil |
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ return "", nil |
|
| 198 |
+} |
|
| 199 |
+ |
|
| 200 |
+func getImageIDAndError(img *image.Image, err error) (string, error) {
|
|
| 201 |
+ if img == nil || err != nil {
|
|
| 202 |
+ return "", err |
|
| 203 |
+ } |
|
| 204 |
+ return img.ID().String(), nil |
|
| 205 |
+} |
|
| 206 |
+ |
|
| 207 |
+func isValidParent(img, parent *image.Image) bool {
|
|
| 208 |
+ if len(img.History) == 0 {
|
|
| 209 |
+ return false |
|
| 210 |
+ } |
|
| 211 |
+ if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
|
|
| 212 |
+ return true |
|
| 213 |
+ } |
|
| 214 |
+ if len(parent.History) >= len(img.History) {
|
|
| 215 |
+ return false |
|
| 216 |
+ } |
|
| 217 |
+ if len(parent.RootFS.DiffIDs) >= len(img.RootFS.DiffIDs) {
|
|
| 218 |
+ return false |
|
| 219 |
+ } |
|
| 220 |
+ |
|
| 221 |
+ for i, h := range parent.History {
|
|
| 222 |
+ if !reflect.DeepEqual(h, img.History[i]) {
|
|
| 223 |
+ return false |
|
| 224 |
+ } |
|
| 225 |
+ } |
|
| 226 |
+ for i, d := range parent.RootFS.DiffIDs {
|
|
| 227 |
+ if d != img.RootFS.DiffIDs[i] {
|
|
| 228 |
+ return false |
|
| 229 |
+ } |
|
| 230 |
+ } |
|
| 231 |
+ return true |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 234 |
+func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID {
|
|
| 235 |
+ layerIndex := 0 |
|
| 236 |
+ for i, h := range image.History {
|
|
| 237 |
+ if i == index {
|
|
| 238 |
+ if h.EmptyLayer {
|
|
| 239 |
+ return "" |
|
| 240 |
+ } |
|
| 241 |
+ break |
|
| 242 |
+ } |
|
| 243 |
+ if !h.EmptyLayer {
|
|
| 244 |
+ layerIndex++ |
|
| 245 |
+ } |
|
| 246 |
+ } |
|
| 247 |
+ return image.RootFS.DiffIDs[layerIndex] // validate? |
|
| 248 |
+} |
|
| 249 |
+ |
|
| 250 |
+func isValidConfig(cfg *containertypes.Config, h image.History) bool {
|
|
| 251 |
+ // todo: make this format better than join that loses data |
|
| 252 |
+ return strings.Join(cfg.Cmd, " ") == h.CreatedBy |
|
| 253 |
+} |
| ... | ... |
@@ -3,11 +3,9 @@ package daemon |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
|
| 6 |
- containertypes "github.com/docker/docker/api/types/container" |
|
| 7 | 6 |
"github.com/docker/docker/builder" |
| 8 | 7 |
"github.com/docker/docker/image" |
| 9 | 8 |
"github.com/docker/docker/reference" |
| 10 |
- "github.com/docker/docker/runconfig" |
|
| 11 | 9 |
) |
| 12 | 10 |
|
| 13 | 11 |
// ErrImageDoesNotExist is error returned when no image can be found for a reference. |
| ... | ... |
@@ -71,54 +69,3 @@ func (daemon *Daemon) GetImageOnBuild(name string) (builder.Image, error) {
|
| 71 | 71 |
} |
| 72 | 72 |
return img, nil |
| 73 | 73 |
} |
| 74 |
- |
|
| 75 |
-// GetCachedImage returns the most recent created image that is a child |
|
| 76 |
-// of the image with imgID, that had the same config when it was |
|
| 77 |
-// created. nil is returned if a child cannot be found. An error is |
|
| 78 |
-// returned if the parent image cannot be found. |
|
| 79 |
-func (daemon *Daemon) GetCachedImage(imgID image.ID, config *containertypes.Config) (*image.Image, error) {
|
|
| 80 |
- // Loop on the children of the given image and check the config |
|
| 81 |
- getMatch := func(siblings []image.ID) (*image.Image, error) {
|
|
| 82 |
- var match *image.Image |
|
| 83 |
- for _, id := range siblings {
|
|
| 84 |
- img, err := daemon.imageStore.Get(id) |
|
| 85 |
- if err != nil {
|
|
| 86 |
- return nil, fmt.Errorf("unable to find image %q", id)
|
|
| 87 |
- } |
|
| 88 |
- |
|
| 89 |
- if runconfig.Compare(&img.ContainerConfig, config) {
|
|
| 90 |
- // check for the most up to date match |
|
| 91 |
- if match == nil || match.Created.Before(img.Created) {
|
|
| 92 |
- match = img |
|
| 93 |
- } |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- return match, nil |
|
| 97 |
- } |
|
| 98 |
- |
|
| 99 |
- // In this case, this is `FROM scratch`, which isn't an actual image. |
|
| 100 |
- if imgID == "" {
|
|
| 101 |
- images := daemon.imageStore.Map() |
|
| 102 |
- var siblings []image.ID |
|
| 103 |
- for id, img := range images {
|
|
| 104 |
- if img.Parent == imgID {
|
|
| 105 |
- siblings = append(siblings, id) |
|
| 106 |
- } |
|
| 107 |
- } |
|
| 108 |
- return getMatch(siblings) |
|
| 109 |
- } |
|
| 110 |
- |
|
| 111 |
- // find match from child images |
|
| 112 |
- siblings := daemon.imageStore.Children(imgID) |
|
| 113 |
- return getMatch(siblings) |
|
| 114 |
-} |
|
| 115 |
- |
|
| 116 |
-// GetCachedImageOnBuild returns a reference to a cached image whose parent equals `parent` |
|
| 117 |
-// and runconfig equals `cfg`. A cache miss is expected to return an empty ID and a nil error. |
|
| 118 |
-func (daemon *Daemon) GetCachedImageOnBuild(imgID string, cfg *containertypes.Config) (string, error) {
|
|
| 119 |
- cache, err := daemon.GetCachedImage(image.ID(imgID), cfg) |
|
| 120 |
- if cache == nil || err != nil {
|
|
| 121 |
- return "", err |
|
| 122 |
- } |
|
| 123 |
- return cache.ID().String(), nil |
|
| 124 |
-} |
| ... | ... |
@@ -124,6 +124,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 124 | 124 |
* `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`). |
| 125 | 125 |
* `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds` and `Volumes`. *note*: `Binds` and `Volumes` are still available but are exclusive with `Mounts` |
| 126 | 126 |
* `POST /build` now performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. Note that this change is _unversioned_ and applied to all API versions. |
| 127 |
+* `POST /build` accepts `cachefrom` parameter to specify images used for build cache. |
|
| 127 | 128 |
|
| 128 | 129 |
### v1.24 API changes |
| 129 | 130 |
|
| ... | ... |
@@ -1715,6 +1715,7 @@ or being killed. |
| 1715 | 1715 |
there must be a file with the corresponding path inside the tarball. |
| 1716 | 1716 |
- **q** – Suppress verbose build output. |
| 1717 | 1717 |
- **nocache** – Do not use the cache when building the image. |
| 1718 |
+- **cachefrom** - JSON array of images used for build cache resolution. |
|
| 1718 | 1719 |
- **pull** - Attempt to pull the image even if an older image exists locally. |
| 1719 | 1720 |
- **rm** - Remove intermediate containers after a successful build (default behavior). |
| 1720 | 1721 |
- **forcerm** - Always remove intermediate containers (includes `rm`). |
| ... | ... |
@@ -106,6 +106,13 @@ the `Using cache` message in the console output. |
| 106 | 106 |
---> 7ea8aef582cc |
| 107 | 107 |
Successfully built 7ea8aef582cc |
| 108 | 108 |
|
| 109 |
+Build cache is only used from images that have a local parent chain. This means |
|
| 110 |
+that these images were created by previous builds or the whole chain of images |
|
| 111 |
+was loaded with `docker load`. If you wish to use build cache of a specific |
|
| 112 |
+image you can specify it with `--cache-from` option. Images specified with |
|
| 113 |
+`--cache-from` do not need to have a parent chain and may be pulled from other |
|
| 114 |
+registries. |
|
| 115 |
+ |
|
| 109 | 116 |
When you're done with your build, you're ready to look into [*Pushing a |
| 110 | 117 |
repository to its registry*](../tutorials/dockerrepos.md#contributing-to-docker-hub). |
| 111 | 118 |
|
| ... | ... |
@@ -17,6 +17,7 @@ Build an image from a Dockerfile |
| 17 | 17 |
|
| 18 | 18 |
Options: |
| 19 | 19 |
--build-arg value Set build-time variables (default []) |
| 20 |
+ --cache-from value Images to consider as cache sources (default []) |
|
| 20 | 21 |
--cgroup-parent string Optional parent cgroup for the container |
| 21 | 22 |
--cpu-period int Limit the CPU CFS (Completely Fair Scheduler) period |
| 22 | 23 |
--cpu-quota int Limit the CPU CFS (Completely Fair Scheduler) quota |