Browse code

vendor: update buildkit to leases support

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2019/10/10 05:22:49
Showing 15 changed files
... ...
@@ -26,7 +26,7 @@ github.com/imdario/mergo                            1afb36080aec31e0d1528973ebe6
26 26
 golang.org/x/sync                                   e225da77a7e68af35c70ccbf71af2b83e6acac3c
27 27
 
28 28
 # buildkit
29
-github.com/moby/buildkit                            f7042823e340d38d1746aa675b83d1aca431cee3
29
+github.com/moby/buildkit                            4f4e03067523b2fc5ca2f17514a5e75ad63e02fb
30 30
 github.com/tonistiigi/fsutil                        3bbb99cdbd76619ab717299830c60f6f2a533a6b
31 31
 github.com/grpc-ecosystem/grpc-opentracing          8e809c8a86450a29b90dcc9efbf062d0fe6d9746
32 32
 github.com/opentracing/opentracing-go               1361b9cd60be79c4c3a7fa9841b3c132e40066a7
... ...
@@ -1,6 +1,6 @@
1 1
 [![asciicinema example](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU.png)](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU)
2 2
 
3
-## BuildKit
3
+# BuildKit
4 4
 
5 5
 [![GoDoc](https://godoc.org/github.com/moby/buildkit?status.svg)](https://godoc.org/github.com/moby/buildkit/client/llb)
6 6
 [![Build Status](https://travis-ci.org/moby/buildkit.svg?branch=master)](https://travis-ci.org/moby/buildkit)
... ...
@@ -25,49 +25,107 @@ Read the proposal from https://github.com/moby/moby/issues/32925
25 25
 
26 26
 Introductory blog post https://blog.mobyproject.org/introducing-buildkit-17e056cc5317
27 27
 
28
+Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack)
29
+
28 30
 :information_source: If you are visiting this repo for the usage of experimental Dockerfile features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)`, please refer to [`frontend/dockerfile/docs/experimental.md`](frontend/dockerfile/docs/experimental.md).
29 31
 
30
-### Used by
32
+:information_source: [BuildKit has been integrated to `docker build` since Docker 18.06 .](https://docs.docker.com/develop/develop-images/build_enhancements/)
33
+You don't need to read this document unless you want to use the full-featured standalone version of BuildKit.
34
+
35
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
36
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
37
+
38
+
39
+- [Used by](#used-by)
40
+- [Quick start](#quick-start)
41
+  - [Starting the `buildkitd` daemon:](#starting-the-buildkitd-daemon)
42
+  - [Exploring LLB](#exploring-llb)
43
+  - [Exploring Dockerfiles](#exploring-dockerfiles)
44
+    - [Building a Dockerfile with `buildctl`](#building-a-dockerfile-with-buildctl)
45
+    - [Building a Dockerfile using external frontend:](#building-a-dockerfile-using-external-frontend)
46
+    - [Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)`](#building-a-dockerfile-with-experimental-features-like-run---mounttypebindcachetmpfssecretssh)
47
+  - [Output](#output)
48
+    - [Registry](#registry)
49
+    - [Local directory](#local-directory)
50
+    - [Docker tarball](#docker-tarball)
51
+    - [OCI tarball](#oci-tarball)
52
+    - [containerd image store](#containerd-image-store)
53
+- [Cache](#cache)
54
+  - [Garbage collection](#garbage-collection)
55
+  - [Export cache](#export-cache)
56
+    - [Inline (push image and cache together)](#inline-push-image-and-cache-together)
57
+    - [Registry (push image and cache separately)](#registry-push-image-and-cache-separately)
58
+    - [Local directory](#local-directory-1)
59
+    - [`--export-cache` options](#--export-cache-options)
60
+    - [`--import-cache` options](#--import-cache-options)
61
+  - [Consistent hashing](#consistent-hashing)
62
+- [Expose BuildKit as a TCP service](#expose-buildkit-as-a-tcp-service)
63
+  - [Load balancing](#load-balancing)
64
+- [Containerizing BuildKit](#containerizing-buildkit)
65
+  - [Kubernetes](#kubernetes)
66
+  - [Daemonless](#daemonless)
67
+- [Opentracing support](#opentracing-support)
68
+- [Running BuildKit without root privileges](#running-buildkit-without-root-privileges)
69
+- [Building multi-platform images](#building-multi-platform-images)
70
+- [Contributing](#contributing)
71
+
72
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
73
+
74
+## Used by
31 75
 
32 76
 BuildKit is used by the following projects:
33 77
 
34
--   [Moby & Docker](https://github.com/moby/moby/pull/37151)
78
+-   [Moby & Docker](https://github.com/moby/moby/pull/37151) (`DOCKER_BUILDKIT=1 docker build`)
35 79
 -   [img](https://github.com/genuinetools/img)
36 80
 -   [OpenFaaS Cloud](https://github.com/openfaas/openfaas-cloud)
37 81
 -   [container build interface](https://github.com/containerbuilding/cbi)
38
--   [Knative Build Templates](https://github.com/knative/build-templates)
82
+-   [Tekton Pipelines](https://github.com/tektoncd/catalog) (formerly [Knative Build Templates](https://github.com/knative/build-templates))
39 83
 -   [the Sanic build tool](https://github.com/distributed-containers-inc/sanic)
40 84
 -   [vab](https://github.com/stellarproject/vab)
41 85
 -   [Rio](https://github.com/rancher/rio)
86
+-   [PouchContainer](https://github.com/alibaba/pouch)
87
+-   [Docker buildx](https://github.com/docker/buildx)
88
+
89
+## Quick start
42 90
 
43
-### Quick start
91
+:information_source: For Kubernetes deployments, see [`examples/kubernetes`](./examples/kubernetes).
44 92
 
45
-Dependencies:
93
+BuildKit is composed of the `buildkitd` daemon and the `buildctl` client.
94
+While the `buildctl` client is available for Linux, macOS, and Windows, the `buildkitd` daemon is only available for Linux currently.
46 95
 
96
+The `buildkitd` daemon requires the following components to be installed:
47 97
 -   [runc](https://github.com/opencontainers/runc)
48 98
 -   [containerd](https://github.com/containerd/containerd) (if you want to use containerd worker)
49 99
 
50
-The following command installs `buildkitd` and `buildctl` to `/usr/local/bin`:
100
+The latest binaries of BuildKit are available [here](https://github.com/moby/buildkit/releases) for Linux, macOS, and Windows.
51 101
 
52
-```bash
53
-$ make && sudo make install
102
+[Homebrew package](https://formulae.brew.sh/formula/buildkit) (unofficial) is available for macOS.
103
+```console
104
+$ brew install buildkit
54 105
 ```
55 106
 
56
-You can also use `make binaries-all` to prepare `buildkitd.containerd_only` and `buildkitd.oci_only`.
107
+To build BuildKit from source, see [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md).
57 108
 
58
-#### Starting the buildkitd daemon:
109
+### Starting the `buildkitd` daemon:
110
+
111
+You need to run `buildkitd` as the root user on the host.
59 112
 
60 113
 ```bash
61
-buildkitd --debug --root /var/lib/buildkit
114
+$ sudo buildkitd
62 115
 ```
63 116
 
117
+To run `buildkitd` as a non-root user, see [`docs/rootless.md`](docs/rootless.md).
118
+
64 119
 The buildkitd daemon supports two worker backends: OCI (runc) and containerd.
65 120
 
66 121
 By default, the OCI (runc) worker is used. You can set `--oci-worker=false --containerd-worker=true` to use the containerd worker.
67 122
 
68 123
 We are open to adding more backends.
69 124
 
70
-#### Exploring LLB
125
+The buildkitd daemon listens gRPC API on `/run/buildkit/buildkitd.sock` by default, but you can also use TCP sockets.
126
+See [Expose BuildKit as a TCP service](#expose-buildkit-as-a-tcp-service).
127
+
128
+### Exploring LLB
71 129
 
72 130
 BuildKit builds are based on a binary intermediate format called LLB that is used for defining the dependency graph for processes running part of your build. tl;dr: LLB is to Dockerfile what LLVM IR is to C.
73 131
 
... ...
@@ -76,49 +134,23 @@ BuildKit builds are based on a binary intermediate format called LLB that is use
76 76
 -   Efficiently cacheable
77 77
 -   Vendor-neutral (i.e. non-Dockerfile languages can be easily implemented)
78 78
 
79
-See [`solver/pb/ops.proto`](./solver/pb/ops.proto) for the format definition.
79
+See [`solver/pb/ops.proto`](./solver/pb/ops.proto) for the format definition, and see [`./examples/README.md`](./examples/README.md) for example LLB applications.
80 80
 
81
-Currently, following high-level languages has been implemented for LLB:
81
+Currently, the following high-level languages has been implemented for LLB:
82 82
 
83 83
 -   Dockerfile (See [Exploring Dockerfiles](#exploring-dockerfiles))
84 84
 -   [Buildpacks](https://github.com/tonistiigi/buildkit-pack)
85
+-   [Mockerfile](https://matt-rickard.com/building-a-new-dockerfile-frontend/)
86
+-   [Gockerfile](https://github.com/po3rin/gockerfile)
85 87
 -   (open a PR to add your own language)
86 88
 
87
-For understanding the basics of LLB, `examples/buildkit*` directory contains scripts that define how to build different configurations of BuildKit itself and its dependencies using the `client` package. Running one of these scripts generates a protobuf definition of a build graph. Note that the script itself does not execute any steps of the build.
88
-
89
-You can use `buildctl debug dump-llb` to see what data is in this definition. Add `--dot` to generate dot layout.
90
-
91
-```bash
92
-go run examples/buildkit0/buildkit.go \
93
-    | buildctl debug dump-llb \
94
-    | jq .
95
-```
96
-
97
-To start building use `buildctl build` command. The example script accepts `--with-containerd` flag to choose if containerd binaries and support should be included in the end result as well.
89
+### Exploring Dockerfiles
98 90
 
99
-```bash
100
-go run examples/buildkit0/buildkit.go \
101
-    | buildctl build
102
-```
103
-
104
-`buildctl build` will show interactive progress bar by default while the build job is running. If the path to the trace file is specified, the trace file generated will contain all information about the timing of the individual steps and logs.
105
-
106
-Different versions of the example scripts show different ways of describing the build definition for this project to show the capabilities of the library. New versions have been added when new features have become available.
107
-
108
--   `./examples/buildkit0` - uses only exec operations, defines a full stage per component.
109
--   `./examples/buildkit1` - cloning git repositories has been separated for extra concurrency.
110
--   `./examples/buildkit2` - uses git sources directly instead of running `git clone`, allowing better performance and much safer caching.
111
--   `./examples/buildkit3` - allows using local source files for separate components eg. `./buildkit3 --runc=local | buildctl build --local runc-src=some/local/path`
112
--   `./examples/dockerfile2llb` - can be used to convert a Dockerfile to LLB for debugging purposes
113
--   `./examples/gobuild` - shows how to use nested invocation to generate LLB for Go package internal dependencies
114
-
115
-#### Exploring Dockerfiles
91
+Frontends are components that run inside BuildKit and convert any build definition to LLB. There is a special frontend called gateway (`gateway.v0`) that allows using any image as a frontend.
116 92
 
117
-Frontends are components that run inside BuildKit and convert any build definition to LLB. There is a special frontend called gateway (gateway.v0) that allows using any image as a frontend.
93
+During development, Dockerfile frontend (`dockerfile.v0`) is also part of the BuildKit repo. In the future, this will be moved out, and Dockerfiles can be built using an external image.
118 94
 
119
-During development, Dockerfile frontend (dockerfile.v0) is also part of the BuildKit repo. In the future, this will be moved out, and Dockerfiles can be built using an external image.
120
-
121
-##### Building a Dockerfile with `buildctl`
95
+#### Building a Dockerfile with `buildctl`
122 96
 
123 97
 ```bash
124 98
 buildctl build \
... ...
@@ -136,22 +168,7 @@ buildctl build \
136 136
 
137 137
 `--local` exposes local source files from client to the builder. `context` and `dockerfile` are the names Dockerfile frontend looks for build context and Dockerfile location.
138 138
 
139
-##### build-using-dockerfile utility
140
-
141
-For people familiar with `docker build` command, there is an example wrapper utility in `./examples/build-using-dockerfile` that allows building Dockerfiles with BuildKit using a syntax similar to `docker build`.
142
-
143
-```bash
144
-go build ./examples/build-using-dockerfile \
145
-    && sudo install build-using-dockerfile /usr/local/bin
146
-
147
-build-using-dockerfile -t myimage .
148
-build-using-dockerfile -t mybuildkit -f ./hack/dockerfiles/test.Dockerfile .
149
-
150
-# build-using-dockerfile will automatically load the resulting image to Docker
151
-docker inspect myimage
152
-```
153
-
154
-##### Building a Dockerfile using [external frontend](https://hub.docker.com/r/docker/dockerfile/tags/):
139
+#### Building a Dockerfile using external frontend:
155 140
 
156 141
 External versions of the Dockerfile frontend are pushed to https://hub.docker.com/r/docker/dockerfile-upstream and https://hub.docker.com/r/docker/dockerfile and can be used with the gateway frontend. The source for the external frontend is currently located in `./frontend/dockerfile/cmd/dockerfile-frontend` but will move out of this repository in the future ([#163](https://github.com/moby/buildkit/issues/163)). For automatic build from master branch of this repository `docker/dockerfile-upsteam:master` or `docker/dockerfile-upstream:master-experimental` image can be used.
157 142
 
... ...
@@ -168,7 +185,7 @@ buildctl build \
168 168
     --opt build-arg:APT_MIRROR=cdn-fastly.deb.debian.org
169 169
 ```
170 170
 
171
-##### Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)`
171
+#### Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)`
172 172
 
173 173
 See [`frontend/dockerfile/docs/experimental.md`](frontend/dockerfile/docs/experimental.md).
174 174
 
... ...
@@ -176,24 +193,26 @@ See [`frontend/dockerfile/docs/experimental.md`](frontend/dockerfile/docs/experi
176 176
 
177 177
 By default, the build result and intermediate cache will only remain internally in BuildKit. An output needs to be specified to retrieve the result.
178 178
 
179
-##### Exporting resulting image to containerd
180
-
181
-The containerd worker needs to be used
179
+#### Registry
182 180
 
183 181
 ```bash
184
-buildctl build ... --output type=image,name=docker.io/username/image
185
-ctr --namespace=buildkit images ls
182
+buildctl build ... --output type=image,name=docker.io/username/image,push=true
186 183
 ```
187 184
 
188
-##### Push resulting image to registry
185
+To export and import the cache along with the image, you need to specify `--export-cache type=inline` and `--import-cache type=registry,ref=...`.
186
+See [Export cache](#export-cache).
189 187
 
190 188
 ```bash
191
-buildctl build ... --output type=image,name=docker.io/username/image,push=true
189
+buildctl build ...\
190
+  --output type=image,name=docker.io/username/image,push=true \
191
+  --export-cache type=inline \
192
+  --import-cache type=registry,ref=docker.io/username/image
192 193
 ```
193 194
 
194
-If credentials are required, `buildctl` will attempt to read Docker configuration file.
195
+If credentials are required, `buildctl` will attempt to read Docker configuration file `$DOCKER_CONFIG/config.json`.
196
+`$DOCKER_CONFIG` defaults to `~/.docker`.
195 197
 
196
-##### Exporting build result back to client
198
+#### Local directory
197 199
 
198 200
 The local client will copy the files directly to the client. This is useful if BuildKit is being used for building something else than container images.
199 201
 
... ...
@@ -222,70 +241,150 @@ buildctl build ... --output type=tar,dest=out.tar
222 222
 buildctl build ... --output type=tar > out.tar
223 223
 ```
224 224
 
225
-##### Exporting built image to Docker
225
+#### Docker tarball
226 226
 
227 227
 ```bash
228 228
 # exported tarball is also compatible with OCI spec
229 229
 buildctl build ... --output type=docker,name=myimage | docker load
230 230
 ```
231 231
 
232
-##### Exporting [OCI Image Format](https://github.com/opencontainers/image-spec) tarball to client
232
+#### OCI tarball
233 233
 
234 234
 ```bash
235 235
 buildctl build ... --output type=oci,dest=path/to/output.tar
236 236
 buildctl build ... --output type=oci > output.tar
237 237
 ```
238
+#### containerd image store
239
+
240
+The containerd worker needs to be used
241
+
242
+```bash
243
+buildctl build ... --output type=image,name=docker.io/username/image
244
+ctr --namespace=buildkit images ls
245
+```
246
+
247
+To change the containerd namespace, you need to change `worker.containerd.namespace` in [`/etc/buildkit/buildkitd.toml`](./docs/buildkitd.toml.md).
248
+
249
+
250
+## Cache
251
+
252
+To show local build cache (`/var/lib/buildkit`):
253
+
254
+```bash
255
+buildctl du -v
256
+```
257
+
258
+To prune local build cache:
259
+```bash
260
+buildctl prune
261
+```
262
+
263
+### Garbage collection
264
+
265
+See [`./docs/buildkitd.toml.md`](./docs/buildkitd.toml.md).
266
+
267
+### Export cache
268
+
269
+BuildKit supports the following cache exporters:
270
+* `inline`: embed the cache into the image, and push them to the registry together
271
+* `registry`: push the image and the cache separately
272
+* `local`: export to a local directory
273
+
274
+In most case you want to use the `inline` cache exporter.
275
+However, note that the `inline` cache exporter only supports `min` cache mode. 
276
+To enable `max` cache mode, push the image and the cache separately by using `registry` cache exporter.
277
+
278
+#### Inline (push image and cache together)
279
+
280
+```bash
281
+buildctl build ... \
282
+  --output type=image,name=docker.io/username/image,push=true \
283
+  --export-cache type=inline \
284
+  --import-cache type=registry,ref=docker.io/username/image
285
+```
286
+
287
+Note that the inline cache is not imported unless `--import-cache type=registry,ref=...` is provided.
238 288
 
239
-### Exporting/Importing build cache (not image itself)
289
+:information_source: Docker-integrated BuildKit (`DOCKER_BUILDKIT=1 docker build`) and `docker buildx`requires 
290
+`--build-arg BUILDKIT_INLINE_CACHE=1` to be specified to enable the `inline` cache exporter.
291
+However, the standalone `buildctl` does NOT require `--opt build-arg:BUILDKIT_INLINE_CACHE=1` and the build-arg is simply ignored.
240 292
 
241
-#### To/From registry
293
+#### Registry (push image and cache separately)
242 294
 
243 295
 ```bash
244
-buildctl build ... --export-cache type=registry,ref=localhost:5000/myrepo:buildcache
245
-buildctl build ... --import-cache type=registry,ref=localhost:5000/myrepo:buildcache
296
+buildctl build ... \
297
+  --output type=image,name=localhost:5000/myrepo:image,push=true \
298
+  --export-cache type=registry,ref=localhost:5000/myrepo:buildcache \
299
+  --import-cache type=registry,ref=localhost:5000/myrepo:buildcache \
246 300
 ```
247 301
 
248
-#### To/From local filesystem
302
+#### Local directory
249 303
 
250 304
 ```bash
251 305
 buildctl build ... --export-cache type=local,dest=path/to/output-dir
252
-buildctl build ... --import-cache type=local,src=path/to/input-dir
306
+buildctl build ... --import-cache type=local,src=path/to/input-dir,digest=sha256:deadbeef
253 307
 ```
254 308
 
255 309
 The directory layout conforms to OCI Image Spec v1.0.
256 310
 
257
-#### `--export-cache` options
311
+Currently, you need to specify the `digest` of the manifest list to import for `local` cache importer. 
312
+This is planned to default to the digest of "latest" tag in `index.json` in future.
258 313
 
314
+#### `--export-cache` options
315
+-   `type`: `inline`, `registry`, or `local`
259 316
 -   `mode=min` (default): only export layers for the resulting image
260
--   `mode=max`: export all the layers of all intermediate steps
317
+-   `mode=max`: export all the layers of all intermediate steps. Not supported for `inline` cache exporter.
261 318
 -   `ref=docker.io/user/image:tag`: reference for `registry` cache exporter
262 319
 -   `dest=path/to/output-dir`: directory for `local` cache exporter
263 320
 
264 321
 #### `--import-cache` options
265
-
322
+-   `type`: `registry` or `local`. Use `registry` to import `inline` cache.
266 323
 -   `ref=docker.io/user/image:tag`: reference for `registry` cache importer
267 324
 -   `src=path/to/input-dir`: directory for `local` cache importer
268
--   `digest=sha256:deadbeef`: digest of the manifest list to import for `local` cache importer. Defaults to the digest of "latest" tag in `index.json`
325
+-   `digest=sha256:deadbeef`: digest of the manifest list to import for `local` cache importer. 
326
+
327
+### Consistent hashing
328
+
329
+If you have multiple BuildKit daemon instances but you don't want to use registry for sharing cache across the cluster,
330
+consider client-side load balancing using consistent hashing.
331
+
332
+See [`./examples/kubernetes/consistenthash`](./examples/kubernetes/consistenthash).
269 333
 
270
-### Other
334
+## Expose BuildKit as a TCP service
271 335
 
272
-#### View build cache
336
+The `buildkitd` daemon can listen the gRPC API on a TCP socket.
337
+
338
+It is highly recommended to create TLS certificates for both the daemon and the client (mTLS).
339
+Enabling TCP without mTLS is dangerous because the executor containers (aka Dockerfile `RUN` containers) can call BuildKit API as well.
273 340
 
274 341
 ```bash
275
-buildctl du -v
342
+buildkitd \
343
+  --addr tcp://0.0.0.0:1234 \
344
+  --tlscacert /path/to/ca.pem \
345
+  --tlscert /path/to/cert.pem \
346
+  --tlskey /path/to/key.pem
276 347
 ```
277 348
 
278
-#### Show enabled workers
279
-
280 349
 ```bash
281
-buildctl debug workers -v
350
+buildctl \
351
+  --addr tcp://example.com:1234 \
352
+  --tlscacert /path/to/ca.pem \
353
+  --tlscert /path/to/clientcert.pem \
354
+  --tlskey /path/to/clientkey.pem \
355
+  build ...
282 356
 ```
283 357
 
284
-### Running containerized buildkit
358
+### Load balancing
359
+
360
+`buildctl build` can be called against randomly load balanced the `buildkitd` daemon.
361
+
362
+See also [Consistent hashing](#consistenthashing) for client-side load balancing.
363
+
364
+## Containerizing BuildKit
285 365
 
286
-BuildKit can also be used by running the `buildkitd` daemon inside a Docker container and accessing it remotely. The client tool `buildctl` is also available for Mac and Windows.
366
+BuildKit can also be used by running the `buildkitd` daemon inside a Docker container and accessing it remotely.
287 367
 
288
-We provide `buildkitd` container images as [`moby/buildkit`](https://hub.docker.com/r/moby/buildkit/tags/):
368
+We provide the container images as [`moby/buildkit`](https://hub.docker.com/r/moby/buildkit/tags/):
289 369
 
290 370
 -   `moby/buildkit:latest`: built from the latest regular [release](https://github.com/moby/buildkit/releases)
291 371
 -   `moby/buildkit:rootless`: same as `latest` but runs as an unprivileged user, see [`docs/rootless.md`](docs/rootless.md)
... ...
@@ -295,11 +394,17 @@ We provide `buildkitd` container images as [`moby/buildkit`](https://hub.docker.
295 295
 To run daemon in a container:
296 296
 
297 297
 ```bash
298
-docker run -d --privileged -p 1234:1234 moby/buildkit:latest --addr tcp://0.0.0.0:1234
299
-export BUILDKIT_HOST=tcp://0.0.0.0:1234
298
+docker run -d --name buildkitd --privileged moby/buildkit:latest
299
+export BUILDKIT_HOST=docker-container://buildkitd
300 300
 buildctl build --help
301 301
 ```
302 302
 
303
+### Kubernetes
304
+
305
+For Kubernetes deployments, see [`examples/kubernetes`](./examples/kubernetes).
306
+
307
+### Daemonless
308
+
303 309
 To run client and an ephemeral daemon in a single container ("daemonless mode"):
304 310
 
305 311
 ```bash
... ...
@@ -335,21 +440,7 @@ docker run \
335 335
         --local dockerfile=/tmp/work
336 336
 ```
337 337
 
338
-The images can be also built locally using `./hack/dockerfiles/test.Dockerfile` (or `./hack/dockerfiles/test.buildkit.Dockerfile` if you already have BuildKit). Run `make images` to build the images as `moby/buildkit:local` and `moby/buildkit:local-rootless`.
339
-
340
-#### Connection helpers
341
-
342
-If you are running `moby/buildkit:master` or `moby/buildkit:master-rootless` as a Docker/Kubernetes container, you can use special `BUILDKIT_HOST` URL for connecting to the BuildKit daemon in the container:
343
-
344
-```bash
345
-export BUILDKIT_HOST=docker-container://<container>
346
-```
347
-
348
-```bash
349
-export BUILDKIT_HOST=kube-pod://<pod>
350
-```
351
-
352
-### Opentracing support
338
+## Opentracing support
353 339
 
354 340
 BuildKit supports opentracing for buildkitd gRPC API and buildctl commands. To capture the trace to [Jaeger](https://github.com/jaegertracing/jaeger), set `JAEGER_TRACE` environment variable to the collection address.
355 341
 
... ...
@@ -360,14 +451,15 @@ export JAEGER_TRACE=0.0.0.0:6831
360 360
 # any buildctl command should be traced to http://127.0.0.1:16686/
361 361
 ```
362 362
 
363
-### Supported runc version
363
+## Running BuildKit without root privileges
364 364
 
365
-During development, BuildKit is tested with the version of runc that is being used by the containerd repository. Please refer to [runc.md](https://github.com/containerd/containerd/blob/v1.2.1/RUNC.md) for more information.
365
+Please refer to [`docs/rootless.md`](docs/rootless.md).
366 366
 
367
-### Running BuildKit without root privileges
367
+## Building multi-platform images
368 368
 
369
-Please refer to [`docs/rootless.md`](docs/rootless.md).
369
+See [`docker buildx` documentation](https://github.com/docker/buildx#building-multi-platform-images)
370 370
 
371
-### Contributing
371
+## Contributing
372 372
 
373 373
 Want to contribute to BuildKit? Awesome! You can find information about contributing to this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md)
374
+
... ...
@@ -6,13 +6,19 @@ import (
6 6
 	"sync"
7 7
 	"time"
8 8
 
9
+	"github.com/containerd/containerd/content"
10
+	"github.com/containerd/containerd/diff"
9 11
 	"github.com/containerd/containerd/filters"
10
-	"github.com/containerd/containerd/snapshots"
12
+	"github.com/containerd/containerd/gc"
13
+	"github.com/containerd/containerd/leases"
11 14
 	"github.com/docker/docker/pkg/idtools"
12 15
 	"github.com/moby/buildkit/cache/metadata"
13 16
 	"github.com/moby/buildkit/client"
14 17
 	"github.com/moby/buildkit/identity"
15 18
 	"github.com/moby/buildkit/snapshot"
19
+	"github.com/opencontainers/go-digest"
20
+	imagespecidentity "github.com/opencontainers/image-spec/identity"
21
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
16 22
 	"github.com/pkg/errors"
17 23
 	"github.com/sirupsen/logrus"
18 24
 	"golang.org/x/sync/errgroup"
... ...
@@ -25,15 +31,20 @@ var (
25 25
 )
26 26
 
27 27
 type ManagerOpt struct {
28
-	Snapshotter     snapshot.SnapshotterBase
28
+	Snapshotter     snapshot.Snapshotter
29 29
 	MetadataStore   *metadata.Store
30
+	ContentStore    content.Store
31
+	LeaseManager    leases.Manager
30 32
 	PruneRefChecker ExternalRefCheckerFunc
33
+	GarbageCollect  func(ctx context.Context) (gc.Stats, error)
34
+	Applier         diff.Applier
31 35
 }
32 36
 
33 37
 type Accessor interface {
38
+	GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ImmutableRef, error)
34 39
 	Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error)
35
-	GetFromSnapshotter(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error)
36
-	New(ctx context.Context, s ImmutableRef, opts ...RefOption) (MutableRef, error)
40
+
41
+	New(ctx context.Context, parent ImmutableRef, opts ...RefOption) (MutableRef, error)
37 42
 	GetMutable(ctx context.Context, id string) (MutableRef, error) // Rebase?
38 43
 	IdentityMapping() *idtools.IdentityMapping
39 44
 	Metadata(string) *metadata.StorageItem
... ...
@@ -53,7 +64,7 @@ type Manager interface {
53 53
 type ExternalRefCheckerFunc func() (ExternalRefChecker, error)
54 54
 
55 55
 type ExternalRefChecker interface {
56
-	Exists(key string) bool
56
+	Exists(string, []digest.Digest) bool
57 57
 }
58 58
 
59 59
 type cacheManager struct {
... ...
@@ -81,6 +92,159 @@ func NewManager(opt ManagerOpt) (Manager, error) {
81 81
 	return cm, nil
82 82
 }
83 83
 
84
+func (cm *cacheManager) GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ir ImmutableRef, err error) {
85
+	diffID, err := diffIDFromDescriptor(desc)
86
+	if err != nil {
87
+		return nil, err
88
+	}
89
+	chainID := diffID
90
+	blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID})
91
+
92
+	if desc.Digest != "" {
93
+		if _, err := cm.ContentStore.Info(ctx, desc.Digest); err != nil {
94
+			return nil, errors.Wrapf(err, "failed to get blob %s", desc.Digest)
95
+		}
96
+	}
97
+
98
+	var p *immutableRef
99
+	var parentID string
100
+	if parent != nil {
101
+		pInfo := parent.Info()
102
+		if pInfo.ChainID == "" || pInfo.BlobChainID == "" {
103
+			return nil, errors.Errorf("failed to get ref by blob on non-adressable parent")
104
+		}
105
+		chainID = imagespecidentity.ChainID([]digest.Digest{pInfo.ChainID, chainID})
106
+		blobChainID = imagespecidentity.ChainID([]digest.Digest{pInfo.BlobChainID, blobChainID})
107
+		p2, err := cm.Get(ctx, parent.ID(), NoUpdateLastUsed)
108
+		if err != nil {
109
+			return nil, err
110
+		}
111
+		if err := p2.Finalize(ctx, true); err != nil {
112
+			return nil, err
113
+		}
114
+		parentID = p2.ID()
115
+		p = p2.(*immutableRef)
116
+	}
117
+
118
+	releaseParent := false
119
+	defer func() {
120
+		if releaseParent || err != nil && p != nil {
121
+			p.Release(context.TODO())
122
+		}
123
+	}()
124
+
125
+	cm.mu.Lock()
126
+	defer cm.mu.Unlock()
127
+
128
+	sis, err := cm.MetadataStore.Search("blobchainid:" + blobChainID.String())
129
+	if err != nil {
130
+		return nil, err
131
+	}
132
+
133
+	for _, si := range sis {
134
+		ref, err := cm.get(ctx, si.ID(), opts...)
135
+		if err != nil && errors.Cause(err) != errNotFound {
136
+			return nil, errors.Wrapf(err, "failed to get record %s by blobchainid", si.ID())
137
+		}
138
+		if p != nil {
139
+			releaseParent = true
140
+		}
141
+		return ref, nil
142
+	}
143
+
144
+	sis, err = cm.MetadataStore.Search("chainid:" + chainID.String())
145
+	if err != nil {
146
+		return nil, err
147
+	}
148
+
149
+	var link ImmutableRef
150
+	for _, si := range sis {
151
+		ref, err := cm.get(ctx, si.ID(), opts...)
152
+		if err != nil && errors.Cause(err) != errNotFound {
153
+			return nil, errors.Wrapf(err, "failed to get record %s by chainid", si.ID())
154
+		}
155
+		link = ref
156
+		break
157
+	}
158
+
159
+	id := identity.NewID()
160
+	snapshotID := chainID.String()
161
+	blobOnly := true
162
+	if link != nil {
163
+		snapshotID = getSnapshotID(link.Metadata())
164
+		blobOnly = getBlobOnly(link.Metadata())
165
+		go link.Release(context.TODO())
166
+	}
167
+
168
+	l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error {
169
+		l.ID = id
170
+		l.Labels = map[string]string{
171
+			"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
172
+		}
173
+		return nil
174
+	})
175
+	if err != nil {
176
+		return nil, errors.Wrap(err, "failed to create lease")
177
+	}
178
+
179
+	defer func() {
180
+		if err != nil {
181
+			if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{
182
+				ID: l.ID,
183
+			}); err != nil {
184
+				logrus.Errorf("failed to remove lease: %+v", err)
185
+			}
186
+		}
187
+	}()
188
+
189
+	if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{
190
+		ID:   snapshotID,
191
+		Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(),
192
+	}); err != nil {
193
+		return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id)
194
+	}
195
+
196
+	if desc.Digest != "" {
197
+		if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{
198
+			ID:   desc.Digest.String(),
199
+			Type: "content",
200
+		}); err != nil {
201
+			return nil, errors.Wrapf(err, "failed to add blob %s to lease", id)
202
+		}
203
+	}
204
+
205
+	md, _ := cm.md.Get(id)
206
+
207
+	rec := &cacheRecord{
208
+		mu:     &sync.Mutex{},
209
+		cm:     cm,
210
+		refs:   make(map[ref]struct{}),
211
+		parent: p,
212
+		md:     md,
213
+	}
214
+
215
+	if err := initializeMetadata(rec, parentID, opts...); err != nil {
216
+		return nil, err
217
+	}
218
+
219
+	queueDiffID(rec.md, diffID.String())
220
+	queueBlob(rec.md, desc.Digest.String())
221
+	queueChainID(rec.md, chainID.String())
222
+	queueBlobChainID(rec.md, blobChainID.String())
223
+	queueSnapshotID(rec.md, snapshotID)
224
+	queueBlobOnly(rec.md, blobOnly)
225
+	queueMediaType(rec.md, desc.MediaType)
226
+	queueCommitted(rec.md)
227
+
228
+	if err := rec.md.Commit(); err != nil {
229
+		return nil, err
230
+	}
231
+
232
+	cm.records[id] = rec
233
+
234
+	return rec.ref(true), nil
235
+}
236
+
84 237
 // init loads all snapshots from metadata state and tries to load the records
85 238
 // from the snapshotter. If snaphot can't be found, metadata is deleted as well.
86 239
 func (cm *cacheManager) init(ctx context.Context) error {
... ...
@@ -90,10 +254,10 @@ func (cm *cacheManager) init(ctx context.Context) error {
90 90
 	}
91 91
 
92 92
 	for _, si := range items {
93
-		if _, err := cm.getRecord(ctx, si.ID(), false); err != nil {
94
-			logrus.Debugf("could not load snapshot %s: %v", si.ID(), err)
93
+		if _, err := cm.getRecord(ctx, si.ID()); err != nil {
94
+			logrus.Debugf("could not load snapshot %s: %+v", si.ID(), err)
95 95
 			cm.md.Clear(si.ID())
96
-			// TODO: make sure content is deleted as well
96
+			cm.LeaseManager.Delete(ctx, leases.Lease{ID: si.ID()})
97 97
 		}
98 98
 	}
99 99
 	return nil
... ...
@@ -115,14 +279,7 @@ func (cm *cacheManager) Close() error {
115 115
 func (cm *cacheManager) Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) {
116 116
 	cm.mu.Lock()
117 117
 	defer cm.mu.Unlock()
118
-	return cm.get(ctx, id, false, opts...)
119
-}
120
-
121
-// Get returns an immutable snapshot reference for ID
122
-func (cm *cacheManager) GetFromSnapshotter(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) {
123
-	cm.mu.Lock()
124
-	defer cm.mu.Unlock()
125
-	return cm.get(ctx, id, true, opts...)
118
+	return cm.get(ctx, id, opts...)
126 119
 }
127 120
 
128 121
 func (cm *cacheManager) Metadata(id string) *metadata.StorageItem {
... ...
@@ -136,8 +293,8 @@ func (cm *cacheManager) Metadata(id string) *metadata.StorageItem {
136 136
 }
137 137
 
138 138
 // get requires manager lock to be taken
139
-func (cm *cacheManager) get(ctx context.Context, id string, fromSnapshotter bool, opts ...RefOption) (*immutableRef, error) {
140
-	rec, err := cm.getRecord(ctx, id, fromSnapshotter, opts...)
139
+func (cm *cacheManager) get(ctx context.Context, id string, opts ...RefOption) (*immutableRef, error) {
140
+	rec, err := cm.getRecord(ctx, id, opts...)
141 141
 	if err != nil {
142 142
 		return nil, err
143 143
 	}
... ...
@@ -165,7 +322,7 @@ func (cm *cacheManager) get(ctx context.Context, id string, fromSnapshotter bool
165 165
 }
166 166
 
167 167
 // getRecord returns record for id. Requires manager lock.
168
-func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotter bool, opts ...RefOption) (cr *cacheRecord, retErr error) {
168
+func (cm *cacheManager) getRecord(ctx context.Context, id string, opts ...RefOption) (cr *cacheRecord, retErr error) {
169 169
 	if rec, ok := cm.records[id]; ok {
170 170
 		if rec.isDead() {
171 171
 			return nil, errors.Wrapf(errNotFound, "failed to get dead record %s", id)
... ...
@@ -174,11 +331,11 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
174 174
 	}
175 175
 
176 176
 	md, ok := cm.md.Get(id)
177
-	if !ok && !fromSnapshotter {
178
-		return nil, errors.WithStack(errNotFound)
177
+	if !ok {
178
+		return nil, errors.Wrapf(errNotFound, "%s not found", id)
179 179
 	}
180 180
 	if mutableID := getEqualMutable(md); mutableID != "" {
181
-		mutable, err := cm.getRecord(ctx, mutableID, fromSnapshotter)
181
+		mutable, err := cm.getRecord(ctx, mutableID)
182 182
 		if err != nil {
183 183
 			// check loading mutable deleted record from disk
184 184
 			if errors.Cause(err) == errNotFound {
... ...
@@ -199,14 +356,10 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
199 199
 		return rec, nil
200 200
 	}
201 201
 
202
-	info, err := cm.Snapshotter.Stat(ctx, id)
203
-	if err != nil {
204
-		return nil, errors.Wrap(errNotFound, err.Error())
205
-	}
206
-
207 202
 	var parent *immutableRef
208
-	if info.Parent != "" {
209
-		parent, err = cm.get(ctx, info.Parent, fromSnapshotter, append(opts, NoUpdateLastUsed)...)
203
+	if parentID := getParent(md); parentID != "" {
204
+		var err error
205
+		parent, err = cm.get(ctx, parentID, append(opts, NoUpdateLastUsed)...)
210 206
 		if err != nil {
211 207
 			return nil, err
212 208
 		}
... ...
@@ -221,7 +374,7 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
221 221
 
222 222
 	rec := &cacheRecord{
223 223
 		mu:      &sync.Mutex{},
224
-		mutable: info.Kind != snapshots.KindCommitted,
224
+		mutable: !getCommitted(md),
225 225
 		cm:      cm,
226 226
 		refs:    make(map[ref]struct{}),
227 227
 		parent:  parent,
... ...
@@ -236,7 +389,7 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
236 236
 		return nil, errors.Wrapf(errNotFound, "failed to get deleted record %s", id)
237 237
 	}
238 238
 
239
-	if err := initializeMetadata(rec, opts...); err != nil {
239
+	if err := initializeMetadata(rec, getParent(md), opts...); err != nil {
240 240
 		return nil, err
241 241
 	}
242 242
 
... ...
@@ -244,11 +397,12 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
244 244
 	return rec, nil
245 245
 }
246 246
 
247
-func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOption) (MutableRef, error) {
247
+func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOption) (mr MutableRef, err error) {
248 248
 	id := identity.NewID()
249 249
 
250 250
 	var parent *immutableRef
251 251
 	var parentID string
252
+	var parentSnapshotID string
252 253
 	if s != nil {
253 254
 		p, err := cm.Get(ctx, s.ID(), NoUpdateLastUsed)
254 255
 		if err != nil {
... ...
@@ -257,14 +411,46 @@ func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOpti
257 257
 		if err := p.Finalize(ctx, true); err != nil {
258 258
 			return nil, err
259 259
 		}
260
-		parentID = p.ID()
261 260
 		parent = p.(*immutableRef)
261
+		parentSnapshotID = getSnapshotID(parent.md)
262
+		parentID = parent.ID()
262 263
 	}
263 264
 
264
-	if err := cm.Snapshotter.Prepare(ctx, id, parentID); err != nil {
265
-		if parent != nil {
265
+	defer func() {
266
+		if err != nil && parent != nil {
266 267
 			parent.Release(context.TODO())
267 268
 		}
269
+	}()
270
+
271
+	l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error {
272
+		l.ID = id
273
+		l.Labels = map[string]string{
274
+			"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
275
+		}
276
+		return nil
277
+	})
278
+	if err != nil {
279
+		return nil, errors.Wrap(err, "failed to create lease")
280
+	}
281
+
282
+	defer func() {
283
+		if err != nil {
284
+			if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{
285
+				ID: l.ID,
286
+			}); err != nil {
287
+				logrus.Errorf("failed to remove lease: %+v", err)
288
+			}
289
+		}
290
+	}()
291
+
292
+	if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{
293
+		ID:   id,
294
+		Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(),
295
+	}); err != nil {
296
+		return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id)
297
+	}
298
+
299
+	if err := cm.Snapshotter.Prepare(ctx, id, parentSnapshotID); err != nil {
268 300
 		return nil, errors.Wrapf(err, "failed to prepare %s", id)
269 301
 	}
270 302
 
... ...
@@ -279,10 +465,7 @@ func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOpti
279 279
 		md:      md,
280 280
 	}
281 281
 
282
-	if err := initializeMetadata(rec, opts...); err != nil {
283
-		if parent != nil {
284
-			parent.Release(context.TODO())
285
-		}
282
+	if err := initializeMetadata(rec, parentID, opts...); err != nil {
286 283
 		return nil, err
287 284
 	}
288 285
 
... ...
@@ -297,7 +480,7 @@ func (cm *cacheManager) GetMutable(ctx context.Context, id string) (MutableRef,
297 297
 	cm.mu.Lock()
298 298
 	defer cm.mu.Unlock()
299 299
 
300
-	rec, err := cm.getRecord(ctx, id, false)
300
+	rec, err := cm.getRecord(ctx, id)
301 301
 	if err != nil {
302 302
 		return nil, err
303 303
 	}
... ...
@@ -328,13 +511,22 @@ func (cm *cacheManager) GetMutable(ctx context.Context, id string) (MutableRef,
328 328
 
329 329
 func (cm *cacheManager) Prune(ctx context.Context, ch chan client.UsageInfo, opts ...client.PruneInfo) error {
330 330
 	cm.muPrune.Lock()
331
-	defer cm.muPrune.Unlock()
332 331
 
333 332
 	for _, opt := range opts {
334 333
 		if err := cm.pruneOnce(ctx, ch, opt); err != nil {
334
+			cm.muPrune.Unlock()
335
+			return err
336
+		}
337
+	}
338
+
339
+	cm.muPrune.Unlock()
340
+
341
+	if cm.GarbageCollect != nil {
342
+		if _, err := cm.GarbageCollect(ctx); err != nil {
335 343
 			return err
336 344
 		}
337 345
 	}
346
+
338 347
 	return nil
339 348
 }
340 349
 
... ...
@@ -360,10 +552,8 @@ func (cm *cacheManager) pruneOnce(ctx context.Context, ch chan client.UsageInfo,
360 360
 			return err
361 361
 		}
362 362
 		for _, ui := range du {
363
-			if check != nil {
364
-				if check.Exists(ui.ID) {
365
-					continue
366
-				}
363
+			if ui.Shared {
364
+				continue
367 365
 			}
368 366
 			totalSize += ui.Size
369 367
 		}
... ...
@@ -418,7 +608,7 @@ func (cm *cacheManager) prune(ctx context.Context, ch chan client.UsageInfo, opt
418 418
 
419 419
 			shared := false
420 420
 			if opt.checkShared != nil {
421
-				shared = opt.checkShared.Exists(cr.ID())
421
+				shared = opt.checkShared.Exists(cr.ID(), cr.parentChain())
422 422
 			}
423 423
 
424 424
 			if !opt.all {
... ...
@@ -577,7 +767,7 @@ func (cm *cacheManager) markShared(m map[string]*cacheUsageInfo) error {
577 577
 		if m[id].shared {
578 578
 			continue
579 579
 		}
580
-		if b := c.Exists(id); b {
580
+		if b := c.Exists(id, m[id].parentChain); b {
581 581
 			markAllParentsShared(id)
582 582
 		}
583 583
 	}
... ...
@@ -596,6 +786,7 @@ type cacheUsageInfo struct {
596 596
 	doubleRef   bool
597 597
 	recordType  client.UsageRecordType
598 598
 	shared      bool
599
+	parentChain []digest.Digest
599 600
 }
600 601
 
601 602
 func (cm *cacheManager) DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error) {
... ...
@@ -628,6 +819,7 @@ func (cm *cacheManager) DiskUsage(ctx context.Context, opt client.DiskUsageInfo)
628 628
 			description: GetDescription(cr.md),
629 629
 			doubleRef:   cr.equalImmutable != nil,
630 630
 			recordType:  GetRecordType(cr),
631
+			parentChain: cr.parentChain(),
631 632
 		}
632 633
 		if c.recordType == "" {
633 634
 			c.recordType = client.UsageRecordTypeRegular
... ...
@@ -769,12 +961,16 @@ func WithCreationTime(tm time.Time) RefOption {
769 769
 	}
770 770
 }
771 771
 
772
-func initializeMetadata(m withMetadata, opts ...RefOption) error {
772
+func initializeMetadata(m withMetadata, parent string, opts ...RefOption) error {
773 773
 	md := m.Metadata()
774 774
 	if tm := GetCreatedAt(md); !tm.IsZero() {
775 775
 		return nil
776 776
 	}
777 777
 
778
+	if err := queueParent(md, parent); err != nil {
779
+		return err
780
+	}
781
+
778 782
 	if err := queueCreatedAt(md, time.Now()); err != nil {
779 783
 		return err
780 784
 	}
... ...
@@ -882,3 +1078,15 @@ func sortDeleteRecords(toDelete []*deleteRecord) {
882 882
 				float64(toDelete[j].usageCountIndex)/float64(maxUsageCountIndex)
883 883
 	})
884 884
 }
885
+
886
+func diffIDFromDescriptor(desc ocispec.Descriptor) (digest.Digest, error) {
887
+	diffIDStr, ok := desc.Annotations["containerd.io/uncompressed"]
888
+	if !ok {
889
+		return "", errors.Errorf("missing uncompressed annotation for %s", desc.Digest)
890
+	}
891
+	diffID, err := digest.Parse(diffIDStr)
892
+	if err != nil {
893
+		return "", errors.Wrapf(err, "failed to parse diffID %q for %s", diffIDStr, desc.Digest)
894
+	}
895
+	return diffID, nil
896
+}
... ...
@@ -19,13 +19,203 @@ const keyLastUsedAt = "cache.lastUsedAt"
19 19
 const keyUsageCount = "cache.usageCount"
20 20
 const keyLayerType = "cache.layerType"
21 21
 const keyRecordType = "cache.recordType"
22
+const keyCommitted = "snapshot.committed"
23
+const keyParent = "cache.parent"
24
+const keyDiffID = "cache.diffID"
25
+const keyChainID = "cache.chainID"
26
+const keyBlobChainID = "cache.blobChainID"
27
+const keyBlob = "cache.blob"
28
+const keySnapshot = "cache.snapshot"
29
+const keyBlobOnly = "cache.blobonly"
30
+const keyMediaType = "cache.mediatype"
22 31
 
23 32
 const keyDeleted = "cache.deleted"
24 33
 
34
+func queueDiffID(si *metadata.StorageItem, str string) error {
35
+	if str == "" {
36
+		return nil
37
+	}
38
+	v, err := metadata.NewValue(str)
39
+	if err != nil {
40
+		return errors.Wrap(err, "failed to create diffID value")
41
+	}
42
+	si.Update(func(b *bolt.Bucket) error {
43
+		return si.SetValue(b, keyDiffID, v)
44
+	})
45
+	return nil
46
+}
47
+
48
+func getMediaType(si *metadata.StorageItem) string {
49
+	v := si.Get(keyMediaType)
50
+	if v == nil {
51
+		return si.ID()
52
+	}
53
+	var str string
54
+	if err := v.Unmarshal(&str); err != nil {
55
+		return ""
56
+	}
57
+	return str
58
+}
59
+
60
+func queueMediaType(si *metadata.StorageItem, str string) error {
61
+	if str == "" {
62
+		return nil
63
+	}
64
+	v, err := metadata.NewValue(str)
65
+	if err != nil {
66
+		return errors.Wrap(err, "failed to create mediaType value")
67
+	}
68
+	si.Queue(func(b *bolt.Bucket) error {
69
+		return si.SetValue(b, keyMediaType, v)
70
+	})
71
+	return nil
72
+}
73
+
74
+func getSnapshotID(si *metadata.StorageItem) string {
75
+	v := si.Get(keySnapshot)
76
+	if v == nil {
77
+		return si.ID()
78
+	}
79
+	var str string
80
+	if err := v.Unmarshal(&str); err != nil {
81
+		return ""
82
+	}
83
+	return str
84
+}
85
+
86
+func queueSnapshotID(si *metadata.StorageItem, str string) error {
87
+	if str == "" {
88
+		return nil
89
+	}
90
+	v, err := metadata.NewValue(str)
91
+	if err != nil {
92
+		return errors.Wrap(err, "failed to create chainID value")
93
+	}
94
+	si.Queue(func(b *bolt.Bucket) error {
95
+		return si.SetValue(b, keySnapshot, v)
96
+	})
97
+	return nil
98
+}
99
+
100
+func getDiffID(si *metadata.StorageItem) string {
101
+	v := si.Get(keyDiffID)
102
+	if v == nil {
103
+		return ""
104
+	}
105
+	var str string
106
+	if err := v.Unmarshal(&str); err != nil {
107
+		return ""
108
+	}
109
+	return str
110
+}
111
+
112
+func queueChainID(si *metadata.StorageItem, str string) error {
113
+	if str == "" {
114
+		return nil
115
+	}
116
+	v, err := metadata.NewValue(str)
117
+	if err != nil {
118
+		return errors.Wrap(err, "failed to create chainID value")
119
+	}
120
+	v.Index = "chainid:" + str
121
+	si.Update(func(b *bolt.Bucket) error {
122
+		return si.SetValue(b, keyChainID, v)
123
+	})
124
+	return nil
125
+}
126
+
127
+func getBlobChainID(si *metadata.StorageItem) string {
128
+	v := si.Get(keyBlobChainID)
129
+	if v == nil {
130
+		return ""
131
+	}
132
+	var str string
133
+	if err := v.Unmarshal(&str); err != nil {
134
+		return ""
135
+	}
136
+	return str
137
+}
138
+
139
+func queueBlobChainID(si *metadata.StorageItem, str string) error {
140
+	if str == "" {
141
+		return nil
142
+	}
143
+	v, err := metadata.NewValue(str)
144
+	if err != nil {
145
+		return errors.Wrap(err, "failed to create chainID value")
146
+	}
147
+	v.Index = "blobchainid:" + str
148
+	si.Update(func(b *bolt.Bucket) error {
149
+		return si.SetValue(b, keyBlobChainID, v)
150
+	})
151
+	return nil
152
+}
153
+
154
+func getChainID(si *metadata.StorageItem) string {
155
+	v := si.Get(keyChainID)
156
+	if v == nil {
157
+		return ""
158
+	}
159
+	var str string
160
+	if err := v.Unmarshal(&str); err != nil {
161
+		return ""
162
+	}
163
+	return str
164
+}
165
+
166
+func queueBlob(si *metadata.StorageItem, str string) error {
167
+	if str == "" {
168
+		return nil
169
+	}
170
+	v, err := metadata.NewValue(str)
171
+	if err != nil {
172
+		return errors.Wrap(err, "failed to create blob value")
173
+	}
174
+	si.Update(func(b *bolt.Bucket) error {
175
+		return si.SetValue(b, keyBlob, v)
176
+	})
177
+	return nil
178
+}
179
+
180
+func getBlob(si *metadata.StorageItem) string {
181
+	v := si.Get(keyBlob)
182
+	if v == nil {
183
+		return ""
184
+	}
185
+	var str string
186
+	if err := v.Unmarshal(&str); err != nil {
187
+		return ""
188
+	}
189
+	return str
190
+}
191
+
192
+func queueBlobOnly(si *metadata.StorageItem, b bool) error {
193
+	v, err := metadata.NewValue(b)
194
+	if err != nil {
195
+		return errors.Wrap(err, "failed to create blobonly value")
196
+	}
197
+	si.Queue(func(b *bolt.Bucket) error {
198
+		return si.SetValue(b, keyBlobOnly, v)
199
+	})
200
+	return nil
201
+}
202
+
203
+func getBlobOnly(si *metadata.StorageItem) bool {
204
+	v := si.Get(keyBlobOnly)
205
+	if v == nil {
206
+		return false
207
+	}
208
+	var blobOnly bool
209
+	if err := v.Unmarshal(&blobOnly); err != nil {
210
+		return false
211
+	}
212
+	return blobOnly
213
+}
214
+
25 215
 func setDeleted(si *metadata.StorageItem) error {
26 216
 	v, err := metadata.NewValue(true)
27 217
 	if err != nil {
28
-		return errors.Wrap(err, "failed to create size value")
218
+		return errors.Wrap(err, "failed to create deleted value")
29 219
 	}
30 220
 	si.Update(func(b *bolt.Bucket) error {
31 221
 		return si.SetValue(b, keyDeleted, v)
... ...
@@ -45,6 +235,55 @@ func getDeleted(si *metadata.StorageItem) bool {
45 45
 	return deleted
46 46
 }
47 47
 
48
+func queueCommitted(si *metadata.StorageItem) error {
49
+	v, err := metadata.NewValue(true)
50
+	if err != nil {
51
+		return errors.Wrap(err, "failed to create committed value")
52
+	}
53
+	si.Queue(func(b *bolt.Bucket) error {
54
+		return si.SetValue(b, keyCommitted, v)
55
+	})
56
+	return nil
57
+}
58
+
59
+func getCommitted(si *metadata.StorageItem) bool {
60
+	v := si.Get(keyCommitted)
61
+	if v == nil {
62
+		return false
63
+	}
64
+	var committed bool
65
+	if err := v.Unmarshal(&committed); err != nil {
66
+		return false
67
+	}
68
+	return committed
69
+}
70
+
71
+func queueParent(si *metadata.StorageItem, parent string) error {
72
+	if parent == "" {
73
+		return nil
74
+	}
75
+	v, err := metadata.NewValue(parent)
76
+	if err != nil {
77
+		return errors.Wrap(err, "failed to create parent value")
78
+	}
79
+	si.Update(func(b *bolt.Bucket) error {
80
+		return si.SetValue(b, keyParent, v)
81
+	})
82
+	return nil
83
+}
84
+
85
+func getParent(si *metadata.StorageItem) string {
86
+	v := si.Get(keyParent)
87
+	if v == nil {
88
+		return ""
89
+	}
90
+	var parent string
91
+	if err := v.Unmarshal(&parent); err != nil {
92
+		return ""
93
+	}
94
+	return parent
95
+}
96
+
48 97
 func setSize(si *metadata.StorageItem, s int64) error {
49 98
 	v, err := metadata.NewValue(s)
50 99
 	if err != nil {
51 100
new file mode 100644
... ...
@@ -0,0 +1,257 @@
0
+package cache
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"os"
6
+	"time"
7
+
8
+	"github.com/containerd/containerd/content"
9
+	"github.com/containerd/containerd/errdefs"
10
+	"github.com/containerd/containerd/images"
11
+	"github.com/containerd/containerd/leases"
12
+	"github.com/containerd/containerd/snapshots"
13
+	"github.com/moby/buildkit/cache/metadata"
14
+	"github.com/moby/buildkit/snapshot"
15
+	"github.com/opencontainers/go-digest"
16
+	"github.com/pkg/errors"
17
+	"github.com/sirupsen/logrus"
18
+)
19
+
20
+func migrateChainID(si *metadata.StorageItem, all map[string]*metadata.StorageItem) (digest.Digest, digest.Digest, error) {
21
+	diffID := digest.Digest(getDiffID(si))
22
+	if diffID == "" {
23
+		return "", "", nil
24
+	}
25
+	blobID := digest.Digest(getBlob(si))
26
+	if blobID == "" {
27
+		return "", "", nil
28
+	}
29
+	chainID := digest.Digest(getChainID(si))
30
+	blobChainID := digest.Digest(getBlobChainID(si))
31
+
32
+	if chainID != "" && blobChainID != "" {
33
+		return chainID, blobChainID, nil
34
+	}
35
+
36
+	chainID = diffID
37
+	blobChainID = digest.FromBytes([]byte(blobID + " " + diffID))
38
+
39
+	parent := getParent(si)
40
+	if parent != "" {
41
+		pChainID, pBlobChainID, err := migrateChainID(all[parent], all)
42
+		if err != nil {
43
+			return "", "", err
44
+		}
45
+		chainID = digest.FromBytes([]byte(pChainID + " " + chainID))
46
+		blobChainID = digest.FromBytes([]byte(pBlobChainID + " " + blobChainID))
47
+	}
48
+
49
+	queueChainID(si, chainID.String())
50
+	queueBlobChainID(si, blobChainID.String())
51
+
52
+	return chainID, blobChainID, si.Commit()
53
+}
54
+
55
+func MigrateV2(ctx context.Context, from, to string, cs content.Store, s snapshot.Snapshotter, lm leases.Manager) error {
56
+	_, err := os.Stat(to)
57
+	if err != nil {
58
+		if !os.IsNotExist(errors.Cause(err)) {
59
+			return errors.WithStack(err)
60
+		}
61
+	} else {
62
+		return nil
63
+	}
64
+
65
+	_, err = os.Stat(from)
66
+	if err != nil {
67
+		if !os.IsNotExist(errors.Cause(err)) {
68
+			return errors.WithStack(err)
69
+		}
70
+		return nil
71
+	}
72
+	tmpPath := to + ".tmp"
73
+	tmpFile, err := os.Create(tmpPath)
74
+	if err != nil {
75
+		return errors.WithStack(err)
76
+	}
77
+	src, err := os.Open(from)
78
+	if err != nil {
79
+		tmpFile.Close()
80
+		return errors.WithStack(err)
81
+	}
82
+	if _, err = io.Copy(tmpFile, src); err != nil {
83
+		tmpFile.Close()
84
+		src.Close()
85
+		return errors.Wrapf(err, "failed to copy db for migration")
86
+	}
87
+	src.Close()
88
+	tmpFile.Close()
89
+
90
+	md, err := metadata.NewStore(tmpPath)
91
+	if err != nil {
92
+		return err
93
+	}
94
+
95
+	items, err := md.All()
96
+	if err != nil {
97
+		return err
98
+	}
99
+
100
+	byID := map[string]*metadata.StorageItem{}
101
+	for _, item := range items {
102
+		byID[item.ID()] = item
103
+	}
104
+
105
+	// add committed, parent, snapshot
106
+	for id, item := range byID {
107
+		em := getEqualMutable(item)
108
+		if em == "" {
109
+			info, err := s.Stat(ctx, id)
110
+			if err != nil {
111
+				return err
112
+			}
113
+			if info.Kind == snapshots.KindCommitted {
114
+				queueCommitted(item)
115
+			}
116
+			if info.Parent != "" {
117
+				queueParent(item, info.Parent)
118
+			}
119
+		} else {
120
+			queueCommitted(item)
121
+		}
122
+		queueSnapshotID(item, id)
123
+		item.Commit()
124
+	}
125
+
126
+	for _, item := range byID {
127
+		em := getEqualMutable(item)
128
+		if em != "" {
129
+			if getParent(item) == "" {
130
+				queueParent(item, getParent(byID[em]))
131
+				item.Commit()
132
+			}
133
+		}
134
+	}
135
+
136
+	type diffPair struct {
137
+		Blobsum string
138
+		DiffID  string
139
+	}
140
+	// move diffID, blobsum to new location
141
+	for _, item := range byID {
142
+		v := item.Get("blobmapping.blob")
143
+		if v == nil {
144
+			continue
145
+		}
146
+		var blob diffPair
147
+		if err := v.Unmarshal(&blob); err != nil {
148
+			return errors.WithStack(err)
149
+		}
150
+		if _, err := cs.Info(ctx, digest.Digest(blob.Blobsum)); err != nil {
151
+			continue
152
+		}
153
+		queueDiffID(item, blob.DiffID)
154
+		queueBlob(item, blob.Blobsum)
155
+		queueMediaType(item, images.MediaTypeDockerSchema2LayerGzip)
156
+		if err := item.Commit(); err != nil {
157
+			return err
158
+		}
159
+
160
+	}
161
+
162
+	// calculate new chainid/blobsumid
163
+	for _, item := range byID {
164
+		if _, _, err := migrateChainID(item, byID); err != nil {
165
+			return err
166
+		}
167
+	}
168
+
169
+	ctx = context.TODO() // no cancellation allowed pass this point
170
+
171
+	// add new leases
172
+	for _, item := range byID {
173
+		l, err := lm.Create(ctx, func(l *leases.Lease) error {
174
+			l.ID = item.ID()
175
+			l.Labels = map[string]string{
176
+				"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
177
+			}
178
+			return nil
179
+		})
180
+		if err != nil {
181
+			// if we are running the migration twice
182
+			if errdefs.IsAlreadyExists(err) {
183
+				continue
184
+			}
185
+			return errors.Wrap(err, "failed to create lease")
186
+		}
187
+
188
+		if err := lm.AddResource(ctx, l, leases.Resource{
189
+			ID:   getSnapshotID(item),
190
+			Type: "snapshots/" + s.Name(),
191
+		}); err != nil {
192
+			return errors.Wrapf(err, "failed to add snapshot %s to lease", item.ID())
193
+		}
194
+
195
+		if blobID := getBlob(item); blobID != "" {
196
+			if err := lm.AddResource(ctx, l, leases.Resource{
197
+				ID:   blobID,
198
+				Type: "content",
199
+			}); err != nil {
200
+				return errors.Wrapf(err, "failed to add blob %s to lease", item.ID())
201
+			}
202
+		}
203
+	}
204
+
205
+	// remove old root labels
206
+	for _, item := range byID {
207
+		if _, err := s.Update(ctx, snapshots.Info{
208
+			Name: getSnapshotID(item),
209
+		}, "labels.containerd.io/gc.root"); err != nil {
210
+			if !errdefs.IsNotFound(errors.Cause(err)) {
211
+				return err
212
+			}
213
+		}
214
+
215
+		if blob := getBlob(item); blob != "" {
216
+			if _, err := cs.Update(ctx, content.Info{
217
+				Digest: digest.Digest(blob),
218
+			}, "labels.containerd.io/gc.root"); err != nil {
219
+				return err
220
+			}
221
+		}
222
+	}
223
+
224
+	// previous implementation can leak views, just clean up all views
225
+	err = s.Walk(ctx, func(ctx context.Context, info snapshots.Info) error {
226
+		if info.Kind == snapshots.KindView {
227
+			if _, err := s.Update(ctx, snapshots.Info{
228
+				Name: info.Name,
229
+			}, "labels.containerd.io/gc.root"); err != nil {
230
+				if !errdefs.IsNotFound(errors.Cause(err)) {
231
+					return err
232
+				}
233
+			}
234
+		}
235
+		return nil
236
+	})
237
+	if err != nil {
238
+		return err
239
+	}
240
+
241
+	// switch to new DB
242
+	if err := md.Close(); err != nil {
243
+		return err
244
+	}
245
+
246
+	if err := os.Rename(tmpPath, to); err != nil {
247
+		return err
248
+	}
249
+
250
+	for _, item := range byID {
251
+		logrus.Infof("migrated %s parent:%q snapshot:%v committed:%v blob:%v diffid:%v chainID:%v blobChainID:%v",
252
+			item.ID(), getParent(item), getSnapshotID(item), getCommitted(item), getBlob(item), getDiffID(item), getChainID(item), getBlobChainID(item))
253
+	}
254
+
255
+	return nil
256
+}
... ...
@@ -2,15 +2,24 @@ package cache
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"fmt"
5 6
 	"strings"
6 7
 	"sync"
8
+	"time"
7 9
 
10
+	"github.com/containerd/containerd/errdefs"
11
+	"github.com/containerd/containerd/leases"
8 12
 	"github.com/containerd/containerd/mount"
13
+	"github.com/containerd/containerd/snapshots"
9 14
 	"github.com/docker/docker/pkg/idtools"
10 15
 	"github.com/moby/buildkit/cache/metadata"
11 16
 	"github.com/moby/buildkit/identity"
12 17
 	"github.com/moby/buildkit/snapshot"
13 18
 	"github.com/moby/buildkit/util/flightcontrol"
19
+	"github.com/moby/buildkit/util/leaseutil"
20
+	"github.com/opencontainers/go-digest"
21
+	imagespecidentity "github.com/opencontainers/image-spec/identity"
22
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
14 23
 	"github.com/pkg/errors"
15 24
 	"github.com/sirupsen/logrus"
16 25
 )
... ...
@@ -30,6 +39,20 @@ type ImmutableRef interface {
30 30
 	Parent() ImmutableRef
31 31
 	Finalize(ctx context.Context, commit bool) error // Make sure reference is flushed to driver
32 32
 	Clone() ImmutableRef
33
+
34
+	Info() RefInfo
35
+	SetBlob(ctx context.Context, desc ocispec.Descriptor) error
36
+	Extract(ctx context.Context) error // +progress
37
+}
38
+
39
+type RefInfo struct {
40
+	SnapshotID  string
41
+	ChainID     digest.Digest
42
+	BlobChainID digest.Digest
43
+	DiffID      digest.Digest
44
+	Blob        digest.Digest
45
+	MediaType   string
46
+	Extracted   bool
33 47
 }
34 48
 
35 49
 type MutableRef interface {
... ...
@@ -65,6 +88,8 @@ type cacheRecord struct {
65 65
 	// these are filled if multiple refs point to same data
66 66
 	equalMutable   *mutableRef
67 67
 	equalImmutable *immutableRef
68
+
69
+	parentChainCache []digest.Digest
68 70
 }
69 71
 
70 72
 // hold ref lock before calling
... ...
@@ -81,6 +106,26 @@ func (cr *cacheRecord) mref(triggerLastUsed bool) *mutableRef {
81 81
 	return ref
82 82
 }
83 83
 
84
+func (cr *cacheRecord) parentChain() []digest.Digest {
85
+	if cr.parentChainCache != nil {
86
+		return cr.parentChainCache
87
+	}
88
+	blob := getBlob(cr.md)
89
+	if blob == "" {
90
+		return nil
91
+	}
92
+
93
+	var parent []digest.Digest
94
+	if cr.parent != nil {
95
+		parent = cr.parent.parentChain()
96
+	}
97
+	pcc := make([]digest.Digest, len(parent)+1)
98
+	copy(pcc, parent)
99
+	pcc[len(parent)] = digest.Digest(blob)
100
+	cr.parentChainCache = pcc
101
+	return pcc
102
+}
103
+
84 104
 // hold ref lock before calling
85 105
 func (cr *cacheRecord) isDead() bool {
86 106
 	return cr.dead || (cr.equalImmutable != nil && cr.equalImmutable.dead) || (cr.equalMutable != nil && cr.equalMutable.dead)
... ...
@@ -99,20 +144,32 @@ func (cr *cacheRecord) Size(ctx context.Context) (int64, error) {
99 99
 			cr.mu.Unlock()
100 100
 			return s, nil
101 101
 		}
102
-		driverID := cr.ID()
102
+		driverID := getSnapshotID(cr.md)
103 103
 		if cr.equalMutable != nil {
104
-			driverID = cr.equalMutable.ID()
104
+			driverID = getSnapshotID(cr.equalMutable.md)
105 105
 		}
106 106
 		cr.mu.Unlock()
107
-		usage, err := cr.cm.ManagerOpt.Snapshotter.Usage(ctx, driverID)
108
-		if err != nil {
109
-			cr.mu.Lock()
110
-			isDead := cr.isDead()
111
-			cr.mu.Unlock()
112
-			if isDead {
113
-				return int64(0), nil
107
+		var usage snapshots.Usage
108
+		if !getBlobOnly(cr.md) {
109
+			var err error
110
+			usage, err = cr.cm.ManagerOpt.Snapshotter.Usage(ctx, driverID)
111
+			if err != nil {
112
+				cr.mu.Lock()
113
+				isDead := cr.isDead()
114
+				cr.mu.Unlock()
115
+				if isDead {
116
+					return int64(0), nil
117
+				}
118
+				if !errdefs.IsNotFound(err) {
119
+					return s, errors.Wrapf(err, "failed to get usage for %s", cr.ID())
120
+				}
121
+			}
122
+		}
123
+		if dgst := getBlob(cr.md); dgst != "" {
124
+			info, err := cr.cm.ContentStore.Info(ctx, digest.Digest(dgst))
125
+			if err == nil {
126
+				usage.Size += info.Size
114 127
 			}
115
-			return s, errors.Wrapf(err, "failed to get usage for %s", cr.ID())
116 128
 		}
117 129
 		cr.mu.Lock()
118 130
 		setSize(cr.md, usage.Size)
... ...
@@ -148,7 +205,7 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount
148 148
 	defer cr.mu.Unlock()
149 149
 
150 150
 	if cr.mutable {
151
-		m, err := cr.cm.Snapshotter.Mounts(ctx, cr.ID())
151
+		m, err := cr.cm.Snapshotter.Mounts(ctx, getSnapshotID(cr.md))
152 152
 		if err != nil {
153 153
 			return nil, errors.Wrapf(err, "failed to mount %s", cr.ID())
154 154
 		}
... ...
@@ -159,7 +216,7 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount
159 159
 	}
160 160
 
161 161
 	if cr.equalMutable != nil && readonly {
162
-		m, err := cr.cm.Snapshotter.Mounts(ctx, cr.equalMutable.ID())
162
+		m, err := cr.cm.Snapshotter.Mounts(ctx, getSnapshotID(cr.equalMutable.md))
163 163
 		if err != nil {
164 164
 			return nil, errors.Wrapf(err, "failed to mount %s", cr.equalMutable.ID())
165 165
 		}
... ...
@@ -170,12 +227,24 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount
170 170
 		return nil, err
171 171
 	}
172 172
 	if cr.viewMount == nil { // TODO: handle this better
173
-		cr.view = identity.NewID()
174
-		m, err := cr.cm.Snapshotter.View(ctx, cr.view, cr.ID())
173
+		view := identity.NewID()
174
+		l, err := cr.cm.LeaseManager.Create(ctx, func(l *leases.Lease) error {
175
+			l.ID = view
176
+			l.Labels = map[string]string{
177
+				"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
178
+			}
179
+			return nil
180
+		}, leaseutil.MakeTemporary)
175 181
 		if err != nil {
176
-			cr.view = ""
182
+			return nil, err
183
+		}
184
+		ctx = leases.WithLease(ctx, l.ID)
185
+		m, err := cr.cm.Snapshotter.View(ctx, view, getSnapshotID(cr.md))
186
+		if err != nil {
187
+			cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: l.ID})
177 188
 			return nil, errors.Wrapf(err, "failed to mount %s", cr.ID())
178 189
 		}
190
+		cr.view = view
179 191
 		cr.viewMount = m
180 192
 	}
181 193
 	return cr.viewMount, nil
... ...
@@ -190,7 +259,7 @@ func (cr *cacheRecord) remove(ctx context.Context, removeSnapshot bool) error {
190 190
 		}
191 191
 	}
192 192
 	if removeSnapshot {
193
-		if err := cr.cm.Snapshotter.Remove(ctx, cr.ID()); err != nil {
193
+		if err := cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()}); err != nil {
194 194
 			return errors.Wrapf(err, "failed to remove %s", cr.ID())
195 195
 		}
196 196
 	}
... ...
@@ -221,6 +290,134 @@ func (sr *immutableRef) Clone() ImmutableRef {
221 221
 	return ref
222 222
 }
223 223
 
224
+func (sr *immutableRef) Info() RefInfo {
225
+	return RefInfo{
226
+		ChainID:     digest.Digest(getChainID(sr.md)),
227
+		DiffID:      digest.Digest(getDiffID(sr.md)),
228
+		Blob:        digest.Digest(getBlob(sr.md)),
229
+		MediaType:   getMediaType(sr.md),
230
+		BlobChainID: digest.Digest(getBlobChainID(sr.md)),
231
+		SnapshotID:  getSnapshotID(sr.md),
232
+		Extracted:   !getBlobOnly(sr.md),
233
+	}
234
+}
235
+
236
+func (sr *immutableRef) Extract(ctx context.Context) error {
237
+	_, err := sr.sizeG.Do(ctx, sr.ID()+"-extract", func(ctx context.Context) (interface{}, error) {
238
+		snapshotID := getSnapshotID(sr.md)
239
+		if _, err := sr.cm.Snapshotter.Stat(ctx, snapshotID); err == nil {
240
+			queueBlobOnly(sr.md, false)
241
+			return nil, sr.md.Commit()
242
+		}
243
+
244
+		parentID := ""
245
+		if sr.parent != nil {
246
+			if err := sr.parent.Extract(ctx); err != nil {
247
+				return nil, err
248
+			}
249
+			parentID = getSnapshotID(sr.parent.md)
250
+		}
251
+		info := sr.Info()
252
+		key := fmt.Sprintf("extract-%s %s", identity.NewID(), info.ChainID)
253
+
254
+		err := sr.cm.Snapshotter.Prepare(ctx, key, parentID)
255
+		if err != nil {
256
+			return nil, err
257
+		}
258
+
259
+		mountable, err := sr.cm.Snapshotter.Mounts(ctx, key)
260
+		if err != nil {
261
+			return nil, err
262
+		}
263
+		mounts, unmount, err := mountable.Mount()
264
+		if err != nil {
265
+			return nil, err
266
+		}
267
+		_, err = sr.cm.Applier.Apply(ctx, ocispec.Descriptor{
268
+			Digest:    info.Blob,
269
+			MediaType: info.MediaType,
270
+		}, mounts)
271
+		if err != nil {
272
+			unmount()
273
+			return nil, err
274
+		}
275
+
276
+		if err := unmount(); err != nil {
277
+			return nil, err
278
+		}
279
+		if err := sr.cm.Snapshotter.Commit(ctx, getSnapshotID(sr.md), key); err != nil {
280
+			if !errdefs.IsAlreadyExists(err) {
281
+				return nil, err
282
+			}
283
+		}
284
+		queueBlobOnly(sr.md, false)
285
+		if err := sr.md.Commit(); err != nil {
286
+			return nil, err
287
+		}
288
+		return nil, nil
289
+	})
290
+	return err
291
+}
292
+
293
+// SetBlob associates a blob with the cache record.
294
+// A lease must be held for the blob when calling this function
295
+// Caller should call Info() for knowing what current values are actually set
296
+func (sr *immutableRef) SetBlob(ctx context.Context, desc ocispec.Descriptor) error {
297
+	diffID, err := diffIDFromDescriptor(desc)
298
+	if err != nil {
299
+		return err
300
+	}
301
+	if _, err := sr.cm.ContentStore.Info(ctx, desc.Digest); err != nil {
302
+		return err
303
+	}
304
+
305
+	sr.mu.Lock()
306
+	defer sr.mu.Unlock()
307
+
308
+	if getChainID(sr.md) != "" {
309
+		return nil
310
+	}
311
+
312
+	if err := sr.finalize(ctx, true); err != nil {
313
+		return err
314
+	}
315
+
316
+	p := sr.parent
317
+	var parentChainID digest.Digest
318
+	var parentBlobChainID digest.Digest
319
+	if p != nil {
320
+		pInfo := p.Info()
321
+		if pInfo.ChainID == "" || pInfo.BlobChainID == "" {
322
+			return errors.Errorf("failed to set blob for reference with non-addressable parent")
323
+		}
324
+		parentChainID = pInfo.ChainID
325
+		parentBlobChainID = pInfo.BlobChainID
326
+	}
327
+
328
+	if err := sr.cm.LeaseManager.AddResource(ctx, leases.Lease{ID: sr.ID()}, leases.Resource{
329
+		ID:   desc.Digest.String(),
330
+		Type: "content",
331
+	}); err != nil {
332
+		return err
333
+	}
334
+
335
+	queueDiffID(sr.md, diffID.String())
336
+	queueBlob(sr.md, desc.Digest.String())
337
+	chainID := diffID
338
+	blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID})
339
+	if parentChainID != "" {
340
+		chainID = imagespecidentity.ChainID([]digest.Digest{parentChainID, chainID})
341
+		blobChainID = imagespecidentity.ChainID([]digest.Digest{parentBlobChainID, blobChainID})
342
+	}
343
+	queueChainID(sr.md, chainID.String())
344
+	queueBlobChainID(sr.md, blobChainID.String())
345
+	queueMediaType(sr.md, desc.MediaType)
346
+	if err := sr.md.Commit(); err != nil {
347
+		return err
348
+	}
349
+	return nil
350
+}
351
+
224 352
 func (sr *immutableRef) Release(ctx context.Context) error {
225 353
 	sr.cm.mu.Lock()
226 354
 	defer sr.cm.mu.Unlock()
... ...
@@ -259,8 +456,8 @@ func (sr *immutableRef) release(ctx context.Context) error {
259 259
 
260 260
 	if len(sr.refs) == 0 {
261 261
 		if sr.viewMount != nil { // TODO: release viewMount earlier if possible
262
-			if err := sr.cm.Snapshotter.Remove(ctx, sr.view); err != nil {
263
-				return errors.Wrapf(err, "failed to remove view %s", sr.view)
262
+			if err := sr.cm.LeaseManager.Delete(ctx, leases.Lease{ID: sr.view}); err != nil {
263
+				return errors.Wrapf(err, "failed to remove view lease %s", sr.view)
264 264
 			}
265 265
 			sr.view = ""
266 266
 			sr.viewMount = nil
... ...
@@ -269,7 +466,6 @@ func (sr *immutableRef) release(ctx context.Context) error {
269 269
 		if sr.equalMutable != nil {
270 270
 			sr.equalMutable.release(ctx)
271 271
 		}
272
-		// go sr.cm.GC()
273 272
 	}
274 273
 
275 274
 	return nil
... ...
@@ -298,18 +494,42 @@ func (cr *cacheRecord) finalize(ctx context.Context, commit bool) error {
298 298
 		}
299 299
 		return nil
300 300
 	}
301
-	err := cr.cm.Snapshotter.Commit(ctx, cr.ID(), mutable.ID())
301
+
302
+	_, err := cr.cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error {
303
+		l.ID = cr.ID()
304
+		l.Labels = map[string]string{
305
+			"containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano),
306
+		}
307
+		return nil
308
+	})
309
+	if err != nil {
310
+		if !errdefs.IsAlreadyExists(err) { // migrator adds leases for everything
311
+			return errors.Wrap(err, "failed to create lease")
312
+		}
313
+	}
314
+
315
+	if err := cr.cm.ManagerOpt.LeaseManager.AddResource(ctx, leases.Lease{ID: cr.ID()}, leases.Resource{
316
+		ID:   cr.ID(),
317
+		Type: "snapshots/" + cr.cm.ManagerOpt.Snapshotter.Name(),
318
+	}); err != nil {
319
+		cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()})
320
+		return errors.Wrapf(err, "failed to add snapshot %s to lease", cr.ID())
321
+	}
322
+
323
+	err = cr.cm.Snapshotter.Commit(ctx, cr.ID(), mutable.ID())
302 324
 	if err != nil {
325
+		cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()})
303 326
 		return errors.Wrapf(err, "failed to commit %s", mutable.ID())
304 327
 	}
305 328
 	mutable.dead = true
306 329
 	go func() {
307 330
 		cr.cm.mu.Lock()
308 331
 		defer cr.cm.mu.Unlock()
309
-		if err := mutable.remove(context.TODO(), false); err != nil {
332
+		if err := mutable.remove(context.TODO(), true); err != nil {
310 333
 			logrus.Error(err)
311 334
 		}
312 335
 	}()
336
+
313 337
 	cr.equalMutable = nil
314 338
 	clearEqualMutable(cr.md)
315 339
 	return cr.md.Commit()
... ...
@@ -341,7 +561,11 @@ func (sr *mutableRef) commit(ctx context.Context) (*immutableRef, error) {
341 341
 		}
342 342
 	}
343 343
 
344
-	if err := initializeMetadata(rec); err != nil {
344
+	parentID := ""
345
+	if rec.parent != nil {
346
+		parentID = rec.parent.ID()
347
+	}
348
+	if err := initializeMetadata(rec, parentID); err != nil {
345 349
 		return nil, err
346 350
 	}
347 351
 
... ...
@@ -351,6 +575,7 @@ func (sr *mutableRef) commit(ctx context.Context) (*immutableRef, error) {
351 351
 		return nil, err
352 352
 	}
353 353
 
354
+	queueCommitted(md)
354 355
 	setSize(md, sizeUnknown)
355 356
 	setEqualMutable(md, sr.ID())
356 357
 	if err := md.Commit(); err != nil {
... ...
@@ -401,11 +626,6 @@ func (sr *mutableRef) release(ctx context.Context) error {
401 401
 				return err
402 402
 			}
403 403
 		}
404
-		if sr.parent != nil {
405
-			if err := sr.parent.release(ctx); err != nil {
406
-				return err
407
-			}
408
-		}
409 404
 		return sr.remove(ctx, true)
410 405
 	} else {
411 406
 		if sr.updateLastUsed() {
... ...
@@ -16,6 +16,9 @@ var g flightcontrol.Group
16 16
 var notFirstRun bool
17 17
 var lastNotEmpty bool
18 18
 
19
+// overridden by tests
20
+var resolvconfGet = resolvconf.Get
21
+
19 22
 type DNSConfig struct {
20 23
 	Nameservers   []string
21 24
 	Options       []string
... ...
@@ -59,7 +62,7 @@ func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.Identity
59 59
 		}
60 60
 
61 61
 		var dt []byte
62
-		f, err := resolvconf.Get()
62
+		f, err := resolvconfGet()
63 63
 		if err != nil {
64 64
 			if !os.IsNotExist(err) {
65 65
 				return "", err
... ...
@@ -88,14 +91,12 @@ func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.Identity
88 88
 			if err != nil {
89 89
 				return "", err
90 90
 			}
91
-		} else {
92
-			// Logic seems odd here: why are we filtering localhost IPs
93
-			// only if neither of the DNS configs were specified?
94
-			// Logic comes from https://github.com/docker/libnetwork/blob/164a77ee6d24fb2b1d61f8ad3403a51d8453899e/sandbox_dns_unix.go#L230-L269
95
-			f, err = resolvconf.FilterResolvDNS(f.Content, true)
96
-			if err != nil {
97
-				return "", err
98
-			}
91
+			dt = f.Content
92
+		}
93
+
94
+		f, err = resolvconf.FilterResolvDNS(dt, true)
95
+		if err != nil {
96
+			return "", err
99 97
 		}
100 98
 
101 99
 		tmpPath := p + ".tmp"
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"context"
6 6
 	"encoding/json"
7 7
 	"fmt"
8
+	"math"
8 9
 	"net/url"
9 10
 	"path"
10 11
 	"path/filepath"
... ...
@@ -1325,7 +1326,7 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform
1325 1325
 		out += ds.stageName + " "
1326 1326
 	}
1327 1327
 	ds.cmdIndex++
1328
-	out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
1328
+	out += fmt.Sprintf("%*d/%d] ", int(1+math.Log10(float64(ds.cmdTotal))), ds.cmdIndex, ds.cmdTotal)
1329 1329
 	return out + str
1330 1330
 }
1331 1331
 
... ...
@@ -9,7 +9,7 @@ require (
9 9
 	github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58 // indirect
10 10
 	github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601 // indirect
11 11
 	github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50
12
-	github.com/containerd/containerd v1.3.0
12
+	github.com/containerd/containerd v1.4.0-0.20191014053712-acdcf13d5eaf
13 13
 	github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6
14 14
 	github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c // indirect
15 15
 	github.com/containerd/go-cni v0.0.0-20190813230227-49fbd9b210f3
16 16
deleted file mode 100644
... ...
@@ -1,151 +0,0 @@
1
-package blobmapping
2
-
3
-import (
4
-	"context"
5
-	"time"
6
-
7
-	"github.com/containerd/containerd/content"
8
-	"github.com/containerd/containerd/snapshots"
9
-	"github.com/moby/buildkit/cache/metadata"
10
-	"github.com/moby/buildkit/snapshot"
11
-	digest "github.com/opencontainers/go-digest"
12
-	"github.com/sirupsen/logrus"
13
-	bolt "go.etcd.io/bbolt"
14
-)
15
-
16
-const blobKey = "blobmapping.blob"
17
-
18
-type Opt struct {
19
-	Content       content.Store
20
-	Snapshotter   snapshot.SnapshotterBase
21
-	MetadataStore *metadata.Store
22
-}
23
-
24
-type Info struct {
25
-	snapshots.Info
26
-	Blob string
27
-}
28
-
29
-type DiffPair struct {
30
-	Blobsum digest.Digest
31
-	DiffID  digest.Digest
32
-}
33
-
34
-// this snapshotter keeps an internal mapping between a snapshot and a blob
35
-
36
-type Snapshotter struct {
37
-	snapshot.SnapshotterBase
38
-	opt Opt
39
-}
40
-
41
-func NewSnapshotter(opt Opt) snapshot.Snapshotter {
42
-	s := &Snapshotter{
43
-		SnapshotterBase: opt.Snapshotter,
44
-		opt:             opt,
45
-	}
46
-
47
-	return s
48
-}
49
-
50
-// Remove also removes a reference to a blob. If it is a last reference then it deletes it the blob as well
51
-// Remove is not safe to be called concurrently
52
-func (s *Snapshotter) Remove(ctx context.Context, key string) error {
53
-	_, blob, err := s.GetBlob(ctx, key)
54
-	if err != nil {
55
-		return err
56
-	}
57
-
58
-	blobs, err := s.opt.MetadataStore.Search(index(blob))
59
-	if err != nil {
60
-		return err
61
-	}
62
-
63
-	if err := s.SnapshotterBase.Remove(ctx, key); err != nil {
64
-		return err
65
-	}
66
-
67
-	if len(blobs) == 1 && blobs[0].ID() == key { // last snapshot
68
-		if err := s.opt.Content.Delete(ctx, blob); err != nil {
69
-			logrus.Errorf("failed to delete blob %v: %+v", blob, err)
70
-		}
71
-	}
72
-	return nil
73
-}
74
-
75
-func (s *Snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
76
-	u, err := s.SnapshotterBase.Usage(ctx, key)
77
-	if err != nil {
78
-		return snapshots.Usage{}, err
79
-	}
80
-	_, blob, err := s.GetBlob(ctx, key)
81
-	if err != nil {
82
-		return u, err
83
-	}
84
-	if blob != "" {
85
-		info, err := s.opt.Content.Info(ctx, blob)
86
-		if err != nil {
87
-			return u, err
88
-		}
89
-		(&u).Add(snapshots.Usage{Size: info.Size, Inodes: 1})
90
-	}
91
-	return u, nil
92
-}
93
-
94
-func (s *Snapshotter) GetBlob(ctx context.Context, key string) (digest.Digest, digest.Digest, error) {
95
-	md, _ := s.opt.MetadataStore.Get(key)
96
-	v := md.Get(blobKey)
97
-	if v == nil {
98
-		return "", "", nil
99
-	}
100
-	var blob DiffPair
101
-	if err := v.Unmarshal(&blob); err != nil {
102
-		return "", "", err
103
-	}
104
-	return blob.DiffID, blob.Blobsum, nil
105
-}
106
-
107
-// Validates that there is no blob associated with the snapshot.
108
-// Checks that there is a blob in the content store.
109
-// If same blob has already been set then this is a noop.
110
-func (s *Snapshotter) SetBlob(ctx context.Context, key string, diffID, blobsum digest.Digest) error {
111
-	info, err := s.opt.Content.Info(ctx, blobsum)
112
-	if err != nil {
113
-		return err
114
-	}
115
-	if _, ok := info.Labels["containerd.io/uncompressed"]; !ok {
116
-		labels := map[string]string{
117
-			"containerd.io/uncompressed": diffID.String(),
118
-		}
119
-		if _, err := s.opt.Content.Update(ctx, content.Info{
120
-			Digest: blobsum,
121
-			Labels: labels,
122
-		}, "labels.containerd.io/uncompressed"); err != nil {
123
-			return err
124
-		}
125
-	}
126
-	// update gc.root cause blob might be held by lease only
127
-	if _, err := s.opt.Content.Update(ctx, content.Info{
128
-		Digest: blobsum,
129
-		Labels: map[string]string{
130
-			"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339Nano),
131
-		},
132
-	}, "labels.containerd.io/gc.root"); err != nil {
133
-		return err
134
-	}
135
-
136
-	md, _ := s.opt.MetadataStore.Get(key)
137
-
138
-	v, err := metadata.NewValue(DiffPair{DiffID: diffID, Blobsum: blobsum})
139
-	if err != nil {
140
-		return err
141
-	}
142
-	v.Index = index(blobsum)
143
-
144
-	return md.Update(func(b *bolt.Bucket) error {
145
-		return md.SetValue(b, blobKey, v)
146
-	})
147
-}
148
-
149
-func index(blob digest.Digest) string {
150
-	return "blobmap::" + blob.String()
151
-}
152 1
new file mode 100644
... ...
@@ -0,0 +1,82 @@
0
+package containerd
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/containerd/containerd/content"
6
+	"github.com/containerd/containerd/namespaces"
7
+	"github.com/opencontainers/go-digest"
8
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
9
+	"github.com/pkg/errors"
10
+)
11
+
12
+func NewContentStore(store content.Store, ns string) content.Store {
13
+	return &nsContent{ns, store}
14
+}
15
+
16
+type nsContent struct {
17
+	ns string
18
+	content.Store
19
+}
20
+
21
+func (c *nsContent) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
22
+	ctx = namespaces.WithNamespace(ctx, c.ns)
23
+	return c.Store.Info(ctx, dgst)
24
+}
25
+
26
+func (c *nsContent) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
27
+	ctx = namespaces.WithNamespace(ctx, c.ns)
28
+	return c.Store.Update(ctx, info, fieldpaths...)
29
+}
30
+
31
+func (c *nsContent) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
32
+	ctx = namespaces.WithNamespace(ctx, c.ns)
33
+	return c.Store.Walk(ctx, fn, filters...)
34
+}
35
+
36
+func (c *nsContent) Delete(ctx context.Context, dgst digest.Digest) error {
37
+	return errors.Errorf("contentstore.Delete usage is forbidden")
38
+}
39
+
40
+func (c *nsContent) Status(ctx context.Context, ref string) (content.Status, error) {
41
+	ctx = namespaces.WithNamespace(ctx, c.ns)
42
+	return c.Store.Status(ctx, ref)
43
+}
44
+
45
+func (c *nsContent) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
46
+	ctx = namespaces.WithNamespace(ctx, c.ns)
47
+	return c.Store.ListStatuses(ctx, filters...)
48
+}
49
+
50
+func (c *nsContent) Abort(ctx context.Context, ref string) error {
51
+	ctx = namespaces.WithNamespace(ctx, c.ns)
52
+	return c.Store.Abort(ctx, ref)
53
+}
54
+
55
+func (c *nsContent) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
56
+	ctx = namespaces.WithNamespace(ctx, c.ns)
57
+	return c.Store.ReaderAt(ctx, desc)
58
+}
59
+
60
+func (c *nsContent) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
61
+	return c.writer(ctx, 3, opts...)
62
+}
63
+
64
+func (c *nsContent) writer(ctx context.Context, retries int, opts ...content.WriterOpt) (content.Writer, error) {
65
+	ctx = namespaces.WithNamespace(ctx, c.ns)
66
+	w, err := c.Store.Writer(ctx, opts...)
67
+	if err != nil {
68
+		return nil, err
69
+	}
70
+	return &nsWriter{Writer: w, ns: c.ns}, nil
71
+}
72
+
73
+type nsWriter struct {
74
+	content.Writer
75
+	ns string
76
+}
77
+
78
+func (w *nsWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
79
+	ctx = namespaces.WithNamespace(ctx, w.ns)
80
+	return w.Writer.Commit(ctx, size, expected, opts...)
81
+}
0 82
new file mode 100644
... ...
@@ -0,0 +1,63 @@
0
+package containerd
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/containerd/containerd/mount"
6
+	"github.com/containerd/containerd/namespaces"
7
+	"github.com/containerd/containerd/snapshots"
8
+	"github.com/docker/docker/pkg/idtools"
9
+	"github.com/moby/buildkit/snapshot"
10
+	"github.com/pkg/errors"
11
+)
12
+
13
+func NewSnapshotter(name string, snapshotter snapshots.Snapshotter, ns string, idmap *idtools.IdentityMapping) snapshot.Snapshotter {
14
+	return snapshot.FromContainerdSnapshotter(name, &nsSnapshotter{ns, snapshotter}, idmap)
15
+}
16
+
17
+func NSSnapshotter(ns string, snapshotter snapshots.Snapshotter) snapshots.Snapshotter {
18
+	return &nsSnapshotter{ns: ns, Snapshotter: snapshotter}
19
+}
20
+
21
+type nsSnapshotter struct {
22
+	ns string
23
+	snapshots.Snapshotter
24
+}
25
+
26
+func (s *nsSnapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
27
+	ctx = namespaces.WithNamespace(ctx, s.ns)
28
+	return s.Snapshotter.Stat(ctx, key)
29
+}
30
+
31
+func (s *nsSnapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
32
+	ctx = namespaces.WithNamespace(ctx, s.ns)
33
+	return s.Snapshotter.Update(ctx, info, fieldpaths...)
34
+}
35
+
36
+func (s *nsSnapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
37
+	ctx = namespaces.WithNamespace(ctx, s.ns)
38
+	return s.Snapshotter.Usage(ctx, key)
39
+}
40
+func (s *nsSnapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
41
+	ctx = namespaces.WithNamespace(ctx, s.ns)
42
+	return s.Snapshotter.Mounts(ctx, key)
43
+}
44
+func (s *nsSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
45
+	ctx = namespaces.WithNamespace(ctx, s.ns)
46
+	return s.Snapshotter.Prepare(ctx, key, parent, opts...)
47
+}
48
+func (s *nsSnapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
49
+	ctx = namespaces.WithNamespace(ctx, s.ns)
50
+	return s.Snapshotter.View(ctx, key, parent, opts...)
51
+}
52
+func (s *nsSnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
53
+	ctx = namespaces.WithNamespace(ctx, s.ns)
54
+	return s.Snapshotter.Commit(ctx, name, key, opts...)
55
+}
56
+func (s *nsSnapshotter) Remove(ctx context.Context, key string) error {
57
+	return errors.Errorf("calling snapshotter.Remove is forbidden")
58
+}
59
+func (s *nsSnapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
60
+	ctx = namespaces.WithNamespace(ctx, s.ns)
61
+	return s.Snapshotter.Walk(ctx, fn)
62
+}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"github.com/containerd/containerd/mount"
10 10
 	"github.com/containerd/containerd/snapshots"
11 11
 	"github.com/docker/docker/pkg/idtools"
12
-	digest "github.com/opencontainers/go-digest"
13 12
 )
14 13
 
15 14
 type Mountable interface {
... ...
@@ -18,7 +17,8 @@ type Mountable interface {
18 18
 	IdentityMapping() *idtools.IdentityMapping
19 19
 }
20 20
 
21
-type SnapshotterBase interface {
21
+// Snapshotter defines interface that any snapshot implementation should satisfy
22
+type Snapshotter interface {
22 23
 	Name() string
23 24
 	Mounts(ctx context.Context, key string) (Mountable, error)
24 25
 	Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) error
... ...
@@ -34,18 +34,7 @@ type SnapshotterBase interface {
34 34
 	IdentityMapping() *idtools.IdentityMapping
35 35
 }
36 36
 
37
-// Snapshotter defines interface that any snapshot implementation should satisfy
38
-type Snapshotter interface {
39
-	Blobmapper
40
-	SnapshotterBase
41
-}
42
-
43
-type Blobmapper interface {
44
-	GetBlob(ctx context.Context, key string) (digest.Digest, digest.Digest, error)
45
-	SetBlob(ctx context.Context, key string, diffID, blob digest.Digest) error
46
-}
47
-
48
-func FromContainerdSnapshotter(name string, s snapshots.Snapshotter, idmap *idtools.IdentityMapping) SnapshotterBase {
37
+func FromContainerdSnapshotter(name string, s snapshots.Snapshotter, idmap *idtools.IdentityMapping) Snapshotter {
49 38
 	return &fromContainerd{name: name, Snapshotter: s, idmap: idmap}
50 39
 }
51 40
 
... ...
@@ -3,7 +3,6 @@ package imageutil
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
-	"fmt"
7 6
 	"sync"
8 7
 	"time"
9 8
 
... ...
@@ -50,7 +49,7 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
50 50
 	}
51 51
 
52 52
 	if leaseManager != nil {
53
-		ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute))
53
+		ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute), leaseutil.MakeTemporary)
54 54
 		if err != nil {
55 55
 			return "", nil, errors.WithStack(err)
56 56
 		}
... ...
@@ -94,12 +93,9 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
94 94
 	}
95 95
 
96 96
 	children := childrenConfigHandler(cache, platform)
97
-	if m, ok := cache.(content.Manager); ok {
98
-		children = SetChildrenLabelsNonBlobs(m, children)
99
-	}
100 97
 
101 98
 	handlers := []images.Handler{
102
-		fetchWithoutRoot(remotes.FetchHandler(cache, fetcher)),
99
+		remotes.FetchHandler(cache, fetcher),
103 100
 		children,
104 101
 	}
105 102
 	if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
... ...
@@ -118,16 +114,6 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
118 118
 	return desc.Digest, dt, nil
119 119
 }
120 120
 
121
-func fetchWithoutRoot(fetch images.HandlerFunc) images.HandlerFunc {
122
-	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
123
-		if desc.Annotations == nil {
124
-			desc.Annotations = map[string]string{}
125
-		}
126
-		desc.Annotations["buildkit/noroot"] = "true"
127
-		return fetch(ctx, desc)
128
-	}
129
-}
130
-
131 121
 func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
132 122
 	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
133 123
 		var descs []specs.Descriptor
... ...
@@ -207,39 +193,3 @@ func DetectManifestBlobMediaType(dt []byte) (string, error) {
207 207
 	}
208 208
 	return images.MediaTypeDockerSchema2ManifestList, nil
209 209
 }
210
-
211
-func SetChildrenLabelsNonBlobs(manager content.Manager, f images.HandlerFunc) images.HandlerFunc {
212
-	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
213
-		children, err := f(ctx, desc)
214
-		if err != nil {
215
-			return children, err
216
-		}
217
-
218
-		if len(children) > 0 {
219
-			info := content.Info{
220
-				Digest: desc.Digest,
221
-				Labels: map[string]string{},
222
-			}
223
-			fields := []string{}
224
-			for i, ch := range children {
225
-				switch ch.MediaType {
226
-				case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip, specs.MediaTypeImageLayer, specs.MediaTypeImageLayerGzip:
227
-					continue
228
-				default:
229
-				}
230
-
231
-				info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String()
232
-				fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i))
233
-			}
234
-
235
-			if len(info.Labels) > 0 {
236
-				_, err := manager.Update(ctx, info, fields...)
237
-				if err != nil {
238
-					return nil, err
239
-				}
240
-			}
241
-		}
242
-
243
-		return children, err
244
-	}
245
-}
... ...
@@ -27,26 +27,49 @@ func WithLease(ctx context.Context, ls leases.Manager, opts ...leases.Opt) (cont
27 27
 	}, nil
28 28
 }
29 29
 
30
+func MakeTemporary(l *leases.Lease) error {
31
+	if l.Labels == nil {
32
+		l.Labels = map[string]string{}
33
+	}
34
+	l.Labels["buildkit/lease.temporary"] = time.Now().UTC().Format(time.RFC3339Nano)
35
+	return nil
36
+}
37
+
30 38
 func WithNamespace(lm leases.Manager, ns string) leases.Manager {
31
-	return &nsLM{Manager: lm, ns: ns}
39
+	return &nsLM{manager: lm, ns: ns}
32 40
 }
33 41
 
34 42
 type nsLM struct {
35
-	leases.Manager
36
-	ns string
43
+	manager leases.Manager
44
+	ns      string
37 45
 }
38 46
 
39 47
 func (l *nsLM) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
40 48
 	ctx = namespaces.WithNamespace(ctx, l.ns)
41
-	return l.Manager.Create(ctx, opts...)
49
+	return l.manager.Create(ctx, opts...)
42 50
 }
43 51
 
44 52
 func (l *nsLM) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error {
45 53
 	ctx = namespaces.WithNamespace(ctx, l.ns)
46
-	return l.Manager.Delete(ctx, lease, opts...)
54
+	return l.manager.Delete(ctx, lease, opts...)
47 55
 }
48 56
 
49 57
 func (l *nsLM) List(ctx context.Context, filters ...string) ([]leases.Lease, error) {
50 58
 	ctx = namespaces.WithNamespace(ctx, l.ns)
51
-	return l.Manager.List(ctx, filters...)
59
+	return l.manager.List(ctx, filters...)
60
+}
61
+
62
+func (l *nsLM) AddResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error {
63
+	ctx = namespaces.WithNamespace(ctx, l.ns)
64
+	return l.manager.AddResource(ctx, lease, resource)
65
+}
66
+
67
+func (l *nsLM) DeleteResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error {
68
+	ctx = namespaces.WithNamespace(ctx, l.ns)
69
+	return l.manager.DeleteResource(ctx, lease, resource)
70
+}
71
+
72
+func (l *nsLM) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
73
+	ctx = namespaces.WithNamespace(ctx, l.ns)
74
+	return l.manager.ListResources(ctx, lease)
52 75
 }