From fb1601d5ab0abd0456b737c2dabd74503cf35de8 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 9 Oct 2019 13:22:49 -0700 Subject: [PATCH 1/5] vendor: update buildkit to leases support Signed-off-by: Tonis Tiigi --- vendor.conf | 2 +- vendor/github.com/moby/buildkit/README.md | 360 +++++++++++------- .../github.com/moby/buildkit/cache/manager.go | 304 ++++++++++++--- .../moby/buildkit/cache/metadata.go | 241 +++++++++++- .../moby/buildkit/cache/migrate_v2.go | 257 +++++++++++++ vendor/github.com/moby/buildkit/cache/refs.go | 274 +++++++++++-- .../moby/buildkit/executor/oci/resolvconf.go | 19 +- .../dockerfile/dockerfile2llb/convert.go | 3 +- vendor/github.com/moby/buildkit/go.mod | 2 +- .../snapshot/blobmapping/snapshotter.go | 151 -------- .../buildkit/snapshot/containerd/content.go | 82 ++++ .../snapshot/containerd/snapshotter.go | 63 +++ .../moby/buildkit/snapshot/snapshotter.go | 17 +- .../moby/buildkit/util/imageutil/config.go | 54 +-- .../moby/buildkit/util/leaseutil/manager.go | 35 +- 15 files changed, 1419 insertions(+), 445 deletions(-) create mode 100644 vendor/github.com/moby/buildkit/cache/migrate_v2.go delete mode 100644 vendor/github.com/moby/buildkit/snapshot/blobmapping/snapshotter.go create mode 100644 vendor/github.com/moby/buildkit/snapshot/containerd/content.go create mode 100644 vendor/github.com/moby/buildkit/snapshot/containerd/snapshotter.go diff --git a/vendor.conf b/vendor.conf index 0be5d884ca..d55418b12a 100644 --- a/vendor.conf +++ b/vendor.conf @@ -26,7 +26,7 @@ github.com/imdario/mergo 1afb36080aec31e0d1528973ebe6 golang.org/x/sync e225da77a7e68af35c70ccbf71af2b83e6acac3c # buildkit -github.com/moby/buildkit f7042823e340d38d1746aa675b83d1aca431cee3 +github.com/moby/buildkit 4f4e03067523b2fc5ca2f17514a5e75ad63e02fb github.com/tonistiigi/fsutil 3bbb99cdbd76619ab717299830c60f6f2a533a6b github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7 diff --git a/vendor/github.com/moby/buildkit/README.md b/vendor/github.com/moby/buildkit/README.md index b75d4c7f80..d2b0a6c7b5 100644 --- a/vendor/github.com/moby/buildkit/README.md +++ b/vendor/github.com/moby/buildkit/README.md @@ -1,6 +1,6 @@ [![asciicinema example](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU.png)](https://asciinema.org/a/gPEIEo1NzmDTUu2bEPsUboqmU) -## BuildKit +# BuildKit [![GoDoc](https://godoc.org/github.com/moby/buildkit?status.svg)](https://godoc.org/github.com/moby/buildkit/client/llb) [![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 Introductory blog post https://blog.mobyproject.org/introducing-buildkit-17e056cc5317 +Join `#buildkit` channel on [Docker Community Slack](http://dockr.ly/slack) + :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). -### Used by +:information_source: [BuildKit has been integrated to `docker build` since Docker 18.06 .](https://docs.docker.com/develop/develop-images/build_enhancements/) +You don't need to read this document unless you want to use the full-featured standalone version of BuildKit. + + + + + +- [Used by](#used-by) +- [Quick start](#quick-start) + - [Starting the `buildkitd` daemon:](#starting-the-buildkitd-daemon) + - [Exploring LLB](#exploring-llb) + - [Exploring Dockerfiles](#exploring-dockerfiles) + - [Building a Dockerfile with `buildctl`](#building-a-dockerfile-with-buildctl) + - [Building a Dockerfile using external frontend:](#building-a-dockerfile-using-external-frontend) + - [Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)`](#building-a-dockerfile-with-experimental-features-like-run---mounttypebindcachetmpfssecretssh) + - [Output](#output) + - [Registry](#registry) + - [Local directory](#local-directory) + - [Docker tarball](#docker-tarball) + - [OCI tarball](#oci-tarball) + - [containerd image store](#containerd-image-store) +- [Cache](#cache) + - [Garbage collection](#garbage-collection) + - [Export cache](#export-cache) + - [Inline (push image and cache together)](#inline-push-image-and-cache-together) + - [Registry (push image and cache separately)](#registry-push-image-and-cache-separately) + - [Local directory](#local-directory-1) + - [`--export-cache` options](#--export-cache-options) + - [`--import-cache` options](#--import-cache-options) + - [Consistent hashing](#consistent-hashing) +- [Expose BuildKit as a TCP service](#expose-buildkit-as-a-tcp-service) + - [Load balancing](#load-balancing) +- [Containerizing BuildKit](#containerizing-buildkit) + - [Kubernetes](#kubernetes) + - [Daemonless](#daemonless) +- [Opentracing support](#opentracing-support) +- [Running BuildKit without root privileges](#running-buildkit-without-root-privileges) +- [Building multi-platform images](#building-multi-platform-images) +- [Contributing](#contributing) + + + +## Used by BuildKit is used by the following projects: -- [Moby & Docker](https://github.com/moby/moby/pull/37151) +- [Moby & Docker](https://github.com/moby/moby/pull/37151) (`DOCKER_BUILDKIT=1 docker build`) - [img](https://github.com/genuinetools/img) - [OpenFaaS Cloud](https://github.com/openfaas/openfaas-cloud) - [container build interface](https://github.com/containerbuilding/cbi) -- [Knative Build Templates](https://github.com/knative/build-templates) +- [Tekton Pipelines](https://github.com/tektoncd/catalog) (formerly [Knative Build Templates](https://github.com/knative/build-templates)) - [the Sanic build tool](https://github.com/distributed-containers-inc/sanic) - [vab](https://github.com/stellarproject/vab) - [Rio](https://github.com/rancher/rio) +- [PouchContainer](https://github.com/alibaba/pouch) +- [Docker buildx](https://github.com/docker/buildx) -### Quick start +## Quick start -Dependencies: +:information_source: For Kubernetes deployments, see [`examples/kubernetes`](./examples/kubernetes). +BuildKit is composed of the `buildkitd` daemon and the `buildctl` client. +While the `buildctl` client is available for Linux, macOS, and Windows, the `buildkitd` daemon is only available for Linux currently. + +The `buildkitd` daemon requires the following components to be installed: - [runc](https://github.com/opencontainers/runc) - [containerd](https://github.com/containerd/containerd) (if you want to use containerd worker) -The following command installs `buildkitd` and `buildctl` to `/usr/local/bin`: +The latest binaries of BuildKit are available [here](https://github.com/moby/buildkit/releases) for Linux, macOS, and Windows. -```bash -$ make && sudo make install +[Homebrew package](https://formulae.brew.sh/formula/buildkit) (unofficial) is available for macOS. +```console +$ brew install buildkit ``` -You can also use `make binaries-all` to prepare `buildkitd.containerd_only` and `buildkitd.oci_only`. +To build BuildKit from source, see [`.github/CONTRIBUTING.md`](./.github/CONTRIBUTING.md). -#### Starting the buildkitd daemon: +### Starting the `buildkitd` daemon: + +You need to run `buildkitd` as the root user on the host. ```bash -buildkitd --debug --root /var/lib/buildkit +$ sudo buildkitd ``` +To run `buildkitd` as a non-root user, see [`docs/rootless.md`](docs/rootless.md). + The buildkitd daemon supports two worker backends: OCI (runc) and containerd. By default, the OCI (runc) worker is used. You can set `--oci-worker=false --containerd-worker=true` to use the containerd worker. We are open to adding more backends. -#### Exploring LLB +The buildkitd daemon listens gRPC API on `/run/buildkit/buildkitd.sock` by default, but you can also use TCP sockets. +See [Expose BuildKit as a TCP service](#expose-buildkit-as-a-tcp-service). + +### Exploring LLB 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. @@ -76,49 +134,23 @@ BuildKit builds are based on a binary intermediate format called LLB that is use - Efficiently cacheable - Vendor-neutral (i.e. non-Dockerfile languages can be easily implemented) -See [`solver/pb/ops.proto`](./solver/pb/ops.proto) for the format definition. +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. -Currently, following high-level languages has been implemented for LLB: +Currently, the following high-level languages has been implemented for LLB: - Dockerfile (See [Exploring Dockerfiles](#exploring-dockerfiles)) - [Buildpacks](https://github.com/tonistiigi/buildkit-pack) +- [Mockerfile](https://matt-rickard.com/building-a-new-dockerfile-frontend/) +- [Gockerfile](https://github.com/po3rin/gockerfile) - (open a PR to add your own language) -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. +### Exploring Dockerfiles -You can use `buildctl debug dump-llb` to see what data is in this definition. Add `--dot` to generate dot layout. +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. -```bash -go run examples/buildkit0/buildkit.go \ - | buildctl debug dump-llb \ - | jq . -``` +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. -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. - -```bash -go run examples/buildkit0/buildkit.go \ - | buildctl build -``` - -`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. - -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. - -- `./examples/buildkit0` - uses only exec operations, defines a full stage per component. -- `./examples/buildkit1` - cloning git repositories has been separated for extra concurrency. -- `./examples/buildkit2` - uses git sources directly instead of running `git clone`, allowing better performance and much safer caching. -- `./examples/buildkit3` - allows using local source files for separate components eg. `./buildkit3 --runc=local | buildctl build --local runc-src=some/local/path` -- `./examples/dockerfile2llb` - can be used to convert a Dockerfile to LLB for debugging purposes -- `./examples/gobuild` - shows how to use nested invocation to generate LLB for Go package internal dependencies - -#### Exploring Dockerfiles - -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. - -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. - -##### Building a Dockerfile with `buildctl` +#### Building a Dockerfile with `buildctl` ```bash buildctl build \ @@ -136,22 +168,7 @@ buildctl build \ `--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. -##### build-using-dockerfile utility - -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`. - -```bash -go build ./examples/build-using-dockerfile \ - && sudo install build-using-dockerfile /usr/local/bin - -build-using-dockerfile -t myimage . -build-using-dockerfile -t mybuildkit -f ./hack/dockerfiles/test.Dockerfile . - -# build-using-dockerfile will automatically load the resulting image to Docker -docker inspect myimage -``` - -##### Building a Dockerfile using [external frontend](https://hub.docker.com/r/docker/dockerfile/tags/): +#### Building a Dockerfile using external frontend: 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. @@ -168,7 +185,7 @@ buildctl build \ --opt build-arg:APT_MIRROR=cdn-fastly.deb.debian.org ``` -##### Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)` +#### Building a Dockerfile with experimental features like `RUN --mount=type=(bind|cache|tmpfs|secret|ssh)` See [`frontend/dockerfile/docs/experimental.md`](frontend/dockerfile/docs/experimental.md). @@ -176,24 +193,26 @@ See [`frontend/dockerfile/docs/experimental.md`](frontend/dockerfile/docs/experi By default, the build result and intermediate cache will only remain internally in BuildKit. An output needs to be specified to retrieve the result. -##### Exporting resulting image to containerd - -The containerd worker needs to be used - -```bash -buildctl build ... --output type=image,name=docker.io/username/image -ctr --namespace=buildkit images ls -``` - -##### Push resulting image to registry +#### Registry ```bash buildctl build ... --output type=image,name=docker.io/username/image,push=true ``` -If credentials are required, `buildctl` will attempt to read Docker configuration file. +To export and import the cache along with the image, you need to specify `--export-cache type=inline` and `--import-cache type=registry,ref=...`. +See [Export cache](#export-cache). -##### Exporting build result back to client +```bash +buildctl build ...\ + --output type=image,name=docker.io/username/image,push=true \ + --export-cache type=inline \ + --import-cache type=registry,ref=docker.io/username/image +``` + +If credentials are required, `buildctl` will attempt to read Docker configuration file `$DOCKER_CONFIG/config.json`. +`$DOCKER_CONFIG` defaults to `~/.docker`. + +#### Local directory 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. @@ -222,70 +241,150 @@ buildctl build ... --output type=tar,dest=out.tar buildctl build ... --output type=tar > out.tar ``` -##### Exporting built image to Docker +#### Docker tarball ```bash # exported tarball is also compatible with OCI spec buildctl build ... --output type=docker,name=myimage | docker load ``` -##### Exporting [OCI Image Format](https://github.com/opencontainers/image-spec) tarball to client +#### OCI tarball ```bash buildctl build ... --output type=oci,dest=path/to/output.tar buildctl build ... --output type=oci > output.tar ``` +#### containerd image store -### Exporting/Importing build cache (not image itself) - -#### To/From registry +The containerd worker needs to be used ```bash -buildctl build ... --export-cache type=registry,ref=localhost:5000/myrepo:buildcache -buildctl build ... --import-cache type=registry,ref=localhost:5000/myrepo:buildcache +buildctl build ... --output type=image,name=docker.io/username/image +ctr --namespace=buildkit images ls ``` -#### To/From local filesystem +To change the containerd namespace, you need to change `worker.containerd.namespace` in [`/etc/buildkit/buildkitd.toml`](./docs/buildkitd.toml.md). -```bash -buildctl build ... --export-cache type=local,dest=path/to/output-dir -buildctl build ... --import-cache type=local,src=path/to/input-dir -``` -The directory layout conforms to OCI Image Spec v1.0. +## Cache -#### `--export-cache` options - -- `mode=min` (default): only export layers for the resulting image -- `mode=max`: export all the layers of all intermediate steps -- `ref=docker.io/user/image:tag`: reference for `registry` cache exporter -- `dest=path/to/output-dir`: directory for `local` cache exporter - -#### `--import-cache` options - -- `ref=docker.io/user/image:tag`: reference for `registry` cache importer -- `src=path/to/input-dir`: directory for `local` cache importer -- `digest=sha256:deadbeef`: digest of the manifest list to import for `local` cache importer. Defaults to the digest of "latest" tag in `index.json` - -### Other - -#### View build cache +To show local build cache (`/var/lib/buildkit`): ```bash buildctl du -v ``` -#### Show enabled workers - +To prune local build cache: ```bash -buildctl debug workers -v +buildctl prune ``` -### Running containerized buildkit +### Garbage collection -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. +See [`./docs/buildkitd.toml.md`](./docs/buildkitd.toml.md). -We provide `buildkitd` container images as [`moby/buildkit`](https://hub.docker.com/r/moby/buildkit/tags/): +### Export cache + +BuildKit supports the following cache exporters: +* `inline`: embed the cache into the image, and push them to the registry together +* `registry`: push the image and the cache separately +* `local`: export to a local directory + +In most case you want to use the `inline` cache exporter. +However, note that the `inline` cache exporter only supports `min` cache mode. +To enable `max` cache mode, push the image and the cache separately by using `registry` cache exporter. + +#### Inline (push image and cache together) + +```bash +buildctl build ... \ + --output type=image,name=docker.io/username/image,push=true \ + --export-cache type=inline \ + --import-cache type=registry,ref=docker.io/username/image +``` + +Note that the inline cache is not imported unless `--import-cache type=registry,ref=...` is provided. + +:information_source: Docker-integrated BuildKit (`DOCKER_BUILDKIT=1 docker build`) and `docker buildx`requires +`--build-arg BUILDKIT_INLINE_CACHE=1` to be specified to enable the `inline` cache exporter. +However, the standalone `buildctl` does NOT require `--opt build-arg:BUILDKIT_INLINE_CACHE=1` and the build-arg is simply ignored. + +#### Registry (push image and cache separately) + +```bash +buildctl build ... \ + --output type=image,name=localhost:5000/myrepo:image,push=true \ + --export-cache type=registry,ref=localhost:5000/myrepo:buildcache \ + --import-cache type=registry,ref=localhost:5000/myrepo:buildcache \ +``` + +#### Local directory + +```bash +buildctl build ... --export-cache type=local,dest=path/to/output-dir +buildctl build ... --import-cache type=local,src=path/to/input-dir,digest=sha256:deadbeef +``` + +The directory layout conforms to OCI Image Spec v1.0. + +Currently, you need to specify the `digest` of the manifest list to import for `local` cache importer. +This is planned to default to the digest of "latest" tag in `index.json` in future. + +#### `--export-cache` options +- `type`: `inline`, `registry`, or `local` +- `mode=min` (default): only export layers for the resulting image +- `mode=max`: export all the layers of all intermediate steps. Not supported for `inline` cache exporter. +- `ref=docker.io/user/image:tag`: reference for `registry` cache exporter +- `dest=path/to/output-dir`: directory for `local` cache exporter + +#### `--import-cache` options +- `type`: `registry` or `local`. Use `registry` to import `inline` cache. +- `ref=docker.io/user/image:tag`: reference for `registry` cache importer +- `src=path/to/input-dir`: directory for `local` cache importer +- `digest=sha256:deadbeef`: digest of the manifest list to import for `local` cache importer. + +### Consistent hashing + +If you have multiple BuildKit daemon instances but you don't want to use registry for sharing cache across the cluster, +consider client-side load balancing using consistent hashing. + +See [`./examples/kubernetes/consistenthash`](./examples/kubernetes/consistenthash). + +## Expose BuildKit as a TCP service + +The `buildkitd` daemon can listen the gRPC API on a TCP socket. + +It is highly recommended to create TLS certificates for both the daemon and the client (mTLS). +Enabling TCP without mTLS is dangerous because the executor containers (aka Dockerfile `RUN` containers) can call BuildKit API as well. + +```bash +buildkitd \ + --addr tcp://0.0.0.0:1234 \ + --tlscacert /path/to/ca.pem \ + --tlscert /path/to/cert.pem \ + --tlskey /path/to/key.pem +``` + +```bash +buildctl \ + --addr tcp://example.com:1234 \ + --tlscacert /path/to/ca.pem \ + --tlscert /path/to/clientcert.pem \ + --tlskey /path/to/clientkey.pem \ + build ... +``` + +### Load balancing + +`buildctl build` can be called against randomly load balanced the `buildkitd` daemon. + +See also [Consistent hashing](#consistenthashing) for client-side load balancing. + +## Containerizing BuildKit + +BuildKit can also be used by running the `buildkitd` daemon inside a Docker container and accessing it remotely. + +We provide the container images as [`moby/buildkit`](https://hub.docker.com/r/moby/buildkit/tags/): - `moby/buildkit:latest`: built from the latest regular [release](https://github.com/moby/buildkit/releases) - `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. To run daemon in a container: ```bash -docker run -d --privileged -p 1234:1234 moby/buildkit:latest --addr tcp://0.0.0.0:1234 -export BUILDKIT_HOST=tcp://0.0.0.0:1234 +docker run -d --name buildkitd --privileged moby/buildkit:latest +export BUILDKIT_HOST=docker-container://buildkitd buildctl build --help ``` +### Kubernetes + +For Kubernetes deployments, see [`examples/kubernetes`](./examples/kubernetes). + +### Daemonless + To run client and an ephemeral daemon in a single container ("daemonless mode"): ```bash @@ -335,21 +440,7 @@ docker run \ --local dockerfile=/tmp/work ``` -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`. - -#### Connection helpers - -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: - -```bash -export BUILDKIT_HOST=docker-container:// -``` - -```bash -export BUILDKIT_HOST=kube-pod:// -``` - -### Opentracing support +## Opentracing support 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. @@ -360,14 +451,15 @@ export JAEGER_TRACE=0.0.0.0:6831 # any buildctl command should be traced to http://127.0.0.1:16686/ ``` -### Supported runc version - -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. - -### Running BuildKit without root privileges +## Running BuildKit without root privileges Please refer to [`docs/rootless.md`](docs/rootless.md). -### Contributing +## Building multi-platform images + +See [`docker buildx` documentation](https://github.com/docker/buildx#building-multi-platform-images) + +## Contributing Want to contribute to BuildKit? Awesome! You can find information about contributing to this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md) + diff --git a/vendor/github.com/moby/buildkit/cache/manager.go b/vendor/github.com/moby/buildkit/cache/manager.go index ebf12e310d..ca6fad86b3 100644 --- a/vendor/github.com/moby/buildkit/cache/manager.go +++ b/vendor/github.com/moby/buildkit/cache/manager.go @@ -6,13 +6,19 @@ import ( "sync" "time" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/diff" "github.com/containerd/containerd/filters" - "github.com/containerd/containerd/snapshots" + "github.com/containerd/containerd/gc" + "github.com/containerd/containerd/leases" "github.com/docker/docker/pkg/idtools" "github.com/moby/buildkit/cache/metadata" "github.com/moby/buildkit/client" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/snapshot" + "github.com/opencontainers/go-digest" + imagespecidentity "github.com/opencontainers/image-spec/identity" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" @@ -25,15 +31,20 @@ var ( ) type ManagerOpt struct { - Snapshotter snapshot.SnapshotterBase + Snapshotter snapshot.Snapshotter MetadataStore *metadata.Store + ContentStore content.Store + LeaseManager leases.Manager PruneRefChecker ExternalRefCheckerFunc + GarbageCollect func(ctx context.Context) (gc.Stats, error) + Applier diff.Applier } type Accessor interface { + GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ImmutableRef, error) Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) - GetFromSnapshotter(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) - New(ctx context.Context, s ImmutableRef, opts ...RefOption) (MutableRef, error) + + New(ctx context.Context, parent ImmutableRef, opts ...RefOption) (MutableRef, error) GetMutable(ctx context.Context, id string) (MutableRef, error) // Rebase? IdentityMapping() *idtools.IdentityMapping Metadata(string) *metadata.StorageItem @@ -53,7 +64,7 @@ type Manager interface { type ExternalRefCheckerFunc func() (ExternalRefChecker, error) type ExternalRefChecker interface { - Exists(key string) bool + Exists(string, []digest.Digest) bool } type cacheManager struct { @@ -81,6 +92,159 @@ func NewManager(opt ManagerOpt) (Manager, error) { return cm, nil } +func (cm *cacheManager) GetByBlob(ctx context.Context, desc ocispec.Descriptor, parent ImmutableRef, opts ...RefOption) (ir ImmutableRef, err error) { + diffID, err := diffIDFromDescriptor(desc) + if err != nil { + return nil, err + } + chainID := diffID + blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID}) + + if desc.Digest != "" { + if _, err := cm.ContentStore.Info(ctx, desc.Digest); err != nil { + return nil, errors.Wrapf(err, "failed to get blob %s", desc.Digest) + } + } + + var p *immutableRef + var parentID string + if parent != nil { + pInfo := parent.Info() + if pInfo.ChainID == "" || pInfo.BlobChainID == "" { + return nil, errors.Errorf("failed to get ref by blob on non-adressable parent") + } + chainID = imagespecidentity.ChainID([]digest.Digest{pInfo.ChainID, chainID}) + blobChainID = imagespecidentity.ChainID([]digest.Digest{pInfo.BlobChainID, blobChainID}) + p2, err := cm.Get(ctx, parent.ID(), NoUpdateLastUsed) + if err != nil { + return nil, err + } + if err := p2.Finalize(ctx, true); err != nil { + return nil, err + } + parentID = p2.ID() + p = p2.(*immutableRef) + } + + releaseParent := false + defer func() { + if releaseParent || err != nil && p != nil { + p.Release(context.TODO()) + } + }() + + cm.mu.Lock() + defer cm.mu.Unlock() + + sis, err := cm.MetadataStore.Search("blobchainid:" + blobChainID.String()) + if err != nil { + return nil, err + } + + for _, si := range sis { + ref, err := cm.get(ctx, si.ID(), opts...) + if err != nil && errors.Cause(err) != errNotFound { + return nil, errors.Wrapf(err, "failed to get record %s by blobchainid", si.ID()) + } + if p != nil { + releaseParent = true + } + return ref, nil + } + + sis, err = cm.MetadataStore.Search("chainid:" + chainID.String()) + if err != nil { + return nil, err + } + + var link ImmutableRef + for _, si := range sis { + ref, err := cm.get(ctx, si.ID(), opts...) + if err != nil && errors.Cause(err) != errNotFound { + return nil, errors.Wrapf(err, "failed to get record %s by chainid", si.ID()) + } + link = ref + break + } + + id := identity.NewID() + snapshotID := chainID.String() + blobOnly := true + if link != nil { + snapshotID = getSnapshotID(link.Metadata()) + blobOnly = getBlobOnly(link.Metadata()) + go link.Release(context.TODO()) + } + + l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error { + l.ID = id + l.Labels = map[string]string{ + "containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano), + } + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "failed to create lease") + } + + defer func() { + if err != nil { + if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{ + ID: l.ID, + }); err != nil { + logrus.Errorf("failed to remove lease: %+v", err) + } + } + }() + + if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{ + ID: snapshotID, + Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(), + }); err != nil { + return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id) + } + + if desc.Digest != "" { + if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, leases.Lease{ID: id}, leases.Resource{ + ID: desc.Digest.String(), + Type: "content", + }); err != nil { + return nil, errors.Wrapf(err, "failed to add blob %s to lease", id) + } + } + + md, _ := cm.md.Get(id) + + rec := &cacheRecord{ + mu: &sync.Mutex{}, + cm: cm, + refs: make(map[ref]struct{}), + parent: p, + md: md, + } + + if err := initializeMetadata(rec, parentID, opts...); err != nil { + return nil, err + } + + queueDiffID(rec.md, diffID.String()) + queueBlob(rec.md, desc.Digest.String()) + queueChainID(rec.md, chainID.String()) + queueBlobChainID(rec.md, blobChainID.String()) + queueSnapshotID(rec.md, snapshotID) + queueBlobOnly(rec.md, blobOnly) + queueMediaType(rec.md, desc.MediaType) + queueCommitted(rec.md) + + if err := rec.md.Commit(); err != nil { + return nil, err + } + + cm.records[id] = rec + + return rec.ref(true), nil +} + // init loads all snapshots from metadata state and tries to load the records // from the snapshotter. If snaphot can't be found, metadata is deleted as well. func (cm *cacheManager) init(ctx context.Context) error { @@ -90,10 +254,10 @@ func (cm *cacheManager) init(ctx context.Context) error { } for _, si := range items { - if _, err := cm.getRecord(ctx, si.ID(), false); err != nil { - logrus.Debugf("could not load snapshot %s: %v", si.ID(), err) + if _, err := cm.getRecord(ctx, si.ID()); err != nil { + logrus.Debugf("could not load snapshot %s: %+v", si.ID(), err) cm.md.Clear(si.ID()) - // TODO: make sure content is deleted as well + cm.LeaseManager.Delete(ctx, leases.Lease{ID: si.ID()}) } } return nil @@ -115,14 +279,7 @@ func (cm *cacheManager) Close() error { func (cm *cacheManager) Get(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) { cm.mu.Lock() defer cm.mu.Unlock() - return cm.get(ctx, id, false, opts...) -} - -// Get returns an immutable snapshot reference for ID -func (cm *cacheManager) GetFromSnapshotter(ctx context.Context, id string, opts ...RefOption) (ImmutableRef, error) { - cm.mu.Lock() - defer cm.mu.Unlock() - return cm.get(ctx, id, true, opts...) + return cm.get(ctx, id, opts...) } func (cm *cacheManager) Metadata(id string) *metadata.StorageItem { @@ -136,8 +293,8 @@ func (cm *cacheManager) Metadata(id string) *metadata.StorageItem { } // get requires manager lock to be taken -func (cm *cacheManager) get(ctx context.Context, id string, fromSnapshotter bool, opts ...RefOption) (*immutableRef, error) { - rec, err := cm.getRecord(ctx, id, fromSnapshotter, opts...) +func (cm *cacheManager) get(ctx context.Context, id string, opts ...RefOption) (*immutableRef, error) { + rec, err := cm.getRecord(ctx, id, opts...) if err != nil { return nil, err } @@ -165,7 +322,7 @@ func (cm *cacheManager) get(ctx context.Context, id string, fromSnapshotter bool } // getRecord returns record for id. Requires manager lock. -func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotter bool, opts ...RefOption) (cr *cacheRecord, retErr error) { +func (cm *cacheManager) getRecord(ctx context.Context, id string, opts ...RefOption) (cr *cacheRecord, retErr error) { if rec, ok := cm.records[id]; ok { if rec.isDead() { 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 } md, ok := cm.md.Get(id) - if !ok && !fromSnapshotter { - return nil, errors.WithStack(errNotFound) + if !ok { + return nil, errors.Wrapf(errNotFound, "%s not found", id) } if mutableID := getEqualMutable(md); mutableID != "" { - mutable, err := cm.getRecord(ctx, mutableID, fromSnapshotter) + mutable, err := cm.getRecord(ctx, mutableID) if err != nil { // check loading mutable deleted record from disk if errors.Cause(err) == errNotFound { @@ -199,14 +356,10 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte return rec, nil } - info, err := cm.Snapshotter.Stat(ctx, id) - if err != nil { - return nil, errors.Wrap(errNotFound, err.Error()) - } - var parent *immutableRef - if info.Parent != "" { - parent, err = cm.get(ctx, info.Parent, fromSnapshotter, append(opts, NoUpdateLastUsed)...) + if parentID := getParent(md); parentID != "" { + var err error + parent, err = cm.get(ctx, parentID, append(opts, NoUpdateLastUsed)...) if err != nil { return nil, err } @@ -221,7 +374,7 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte rec := &cacheRecord{ mu: &sync.Mutex{}, - mutable: info.Kind != snapshots.KindCommitted, + mutable: !getCommitted(md), cm: cm, refs: make(map[ref]struct{}), parent: parent, @@ -236,7 +389,7 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte return nil, errors.Wrapf(errNotFound, "failed to get deleted record %s", id) } - if err := initializeMetadata(rec, opts...); err != nil { + if err := initializeMetadata(rec, getParent(md), opts...); err != nil { return nil, err } @@ -244,11 +397,12 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte return rec, nil } -func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOption) (MutableRef, error) { +func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOption) (mr MutableRef, err error) { id := identity.NewID() var parent *immutableRef var parentID string + var parentSnapshotID string if s != nil { p, err := cm.Get(ctx, s.ID(), NoUpdateLastUsed) if err != nil { @@ -257,14 +411,46 @@ func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOpti if err := p.Finalize(ctx, true); err != nil { return nil, err } - parentID = p.ID() parent = p.(*immutableRef) + parentSnapshotID = getSnapshotID(parent.md) + parentID = parent.ID() } - if err := cm.Snapshotter.Prepare(ctx, id, parentID); err != nil { - if parent != nil { + defer func() { + if err != nil && parent != nil { parent.Release(context.TODO()) } + }() + + l, err := cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error { + l.ID = id + l.Labels = map[string]string{ + "containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano), + } + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "failed to create lease") + } + + defer func() { + if err != nil { + if err := cm.ManagerOpt.LeaseManager.Delete(context.TODO(), leases.Lease{ + ID: l.ID, + }); err != nil { + logrus.Errorf("failed to remove lease: %+v", err) + } + } + }() + + if err := cm.ManagerOpt.LeaseManager.AddResource(ctx, l, leases.Resource{ + ID: id, + Type: "snapshots/" + cm.ManagerOpt.Snapshotter.Name(), + }); err != nil { + return nil, errors.Wrapf(err, "failed to add snapshot %s to lease", id) + } + + if err := cm.Snapshotter.Prepare(ctx, id, parentSnapshotID); err != nil { return nil, errors.Wrapf(err, "failed to prepare %s", id) } @@ -279,10 +465,7 @@ func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, opts ...RefOpti md: md, } - if err := initializeMetadata(rec, opts...); err != nil { - if parent != nil { - parent.Release(context.TODO()) - } + if err := initializeMetadata(rec, parentID, opts...); err != nil { return nil, err } @@ -297,7 +480,7 @@ func (cm *cacheManager) GetMutable(ctx context.Context, id string) (MutableRef, cm.mu.Lock() defer cm.mu.Unlock() - rec, err := cm.getRecord(ctx, id, false) + rec, err := cm.getRecord(ctx, id) if err != nil { return nil, err } @@ -328,13 +511,22 @@ func (cm *cacheManager) GetMutable(ctx context.Context, id string) (MutableRef, func (cm *cacheManager) Prune(ctx context.Context, ch chan client.UsageInfo, opts ...client.PruneInfo) error { cm.muPrune.Lock() - defer cm.muPrune.Unlock() for _, opt := range opts { if err := cm.pruneOnce(ctx, ch, opt); err != nil { + cm.muPrune.Unlock() return err } } + + cm.muPrune.Unlock() + + if cm.GarbageCollect != nil { + if _, err := cm.GarbageCollect(ctx); err != nil { + return err + } + } + return nil } @@ -360,10 +552,8 @@ func (cm *cacheManager) pruneOnce(ctx context.Context, ch chan client.UsageInfo, return err } for _, ui := range du { - if check != nil { - if check.Exists(ui.ID) { - continue - } + if ui.Shared { + continue } totalSize += ui.Size } @@ -418,7 +608,7 @@ func (cm *cacheManager) prune(ctx context.Context, ch chan client.UsageInfo, opt shared := false if opt.checkShared != nil { - shared = opt.checkShared.Exists(cr.ID()) + shared = opt.checkShared.Exists(cr.ID(), cr.parentChain()) } if !opt.all { @@ -577,7 +767,7 @@ func (cm *cacheManager) markShared(m map[string]*cacheUsageInfo) error { if m[id].shared { continue } - if b := c.Exists(id); b { + if b := c.Exists(id, m[id].parentChain); b { markAllParentsShared(id) } } @@ -596,6 +786,7 @@ type cacheUsageInfo struct { doubleRef bool recordType client.UsageRecordType shared bool + parentChain []digest.Digest } 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) description: GetDescription(cr.md), doubleRef: cr.equalImmutable != nil, recordType: GetRecordType(cr), + parentChain: cr.parentChain(), } if c.recordType == "" { c.recordType = client.UsageRecordTypeRegular @@ -769,12 +961,16 @@ func WithCreationTime(tm time.Time) RefOption { } } -func initializeMetadata(m withMetadata, opts ...RefOption) error { +func initializeMetadata(m withMetadata, parent string, opts ...RefOption) error { md := m.Metadata() if tm := GetCreatedAt(md); !tm.IsZero() { return nil } + if err := queueParent(md, parent); err != nil { + return err + } + if err := queueCreatedAt(md, time.Now()); err != nil { return err } @@ -882,3 +1078,15 @@ func sortDeleteRecords(toDelete []*deleteRecord) { float64(toDelete[j].usageCountIndex)/float64(maxUsageCountIndex) }) } + +func diffIDFromDescriptor(desc ocispec.Descriptor) (digest.Digest, error) { + diffIDStr, ok := desc.Annotations["containerd.io/uncompressed"] + if !ok { + return "", errors.Errorf("missing uncompressed annotation for %s", desc.Digest) + } + diffID, err := digest.Parse(diffIDStr) + if err != nil { + return "", errors.Wrapf(err, "failed to parse diffID %q for %s", diffIDStr, desc.Digest) + } + return diffID, nil +} diff --git a/vendor/github.com/moby/buildkit/cache/metadata.go b/vendor/github.com/moby/buildkit/cache/metadata.go index 9929868844..bf4041e098 100644 --- a/vendor/github.com/moby/buildkit/cache/metadata.go +++ b/vendor/github.com/moby/buildkit/cache/metadata.go @@ -19,13 +19,203 @@ const keyLastUsedAt = "cache.lastUsedAt" const keyUsageCount = "cache.usageCount" const keyLayerType = "cache.layerType" const keyRecordType = "cache.recordType" +const keyCommitted = "snapshot.committed" +const keyParent = "cache.parent" +const keyDiffID = "cache.diffID" +const keyChainID = "cache.chainID" +const keyBlobChainID = "cache.blobChainID" +const keyBlob = "cache.blob" +const keySnapshot = "cache.snapshot" +const keyBlobOnly = "cache.blobonly" +const keyMediaType = "cache.mediatype" const keyDeleted = "cache.deleted" +func queueDiffID(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create diffID value") + } + si.Update(func(b *bolt.Bucket) error { + return si.SetValue(b, keyDiffID, v) + }) + return nil +} + +func getMediaType(si *metadata.StorageItem) string { + v := si.Get(keyMediaType) + if v == nil { + return si.ID() + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueMediaType(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create mediaType value") + } + si.Queue(func(b *bolt.Bucket) error { + return si.SetValue(b, keyMediaType, v) + }) + return nil +} + +func getSnapshotID(si *metadata.StorageItem) string { + v := si.Get(keySnapshot) + if v == nil { + return si.ID() + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueSnapshotID(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create chainID value") + } + si.Queue(func(b *bolt.Bucket) error { + return si.SetValue(b, keySnapshot, v) + }) + return nil +} + +func getDiffID(si *metadata.StorageItem) string { + v := si.Get(keyDiffID) + if v == nil { + return "" + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueChainID(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create chainID value") + } + v.Index = "chainid:" + str + si.Update(func(b *bolt.Bucket) error { + return si.SetValue(b, keyChainID, v) + }) + return nil +} + +func getBlobChainID(si *metadata.StorageItem) string { + v := si.Get(keyBlobChainID) + if v == nil { + return "" + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueBlobChainID(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create chainID value") + } + v.Index = "blobchainid:" + str + si.Update(func(b *bolt.Bucket) error { + return si.SetValue(b, keyBlobChainID, v) + }) + return nil +} + +func getChainID(si *metadata.StorageItem) string { + v := si.Get(keyChainID) + if v == nil { + return "" + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueBlob(si *metadata.StorageItem, str string) error { + if str == "" { + return nil + } + v, err := metadata.NewValue(str) + if err != nil { + return errors.Wrap(err, "failed to create blob value") + } + si.Update(func(b *bolt.Bucket) error { + return si.SetValue(b, keyBlob, v) + }) + return nil +} + +func getBlob(si *metadata.StorageItem) string { + v := si.Get(keyBlob) + if v == nil { + return "" + } + var str string + if err := v.Unmarshal(&str); err != nil { + return "" + } + return str +} + +func queueBlobOnly(si *metadata.StorageItem, b bool) error { + v, err := metadata.NewValue(b) + if err != nil { + return errors.Wrap(err, "failed to create blobonly value") + } + si.Queue(func(b *bolt.Bucket) error { + return si.SetValue(b, keyBlobOnly, v) + }) + return nil +} + +func getBlobOnly(si *metadata.StorageItem) bool { + v := si.Get(keyBlobOnly) + if v == nil { + return false + } + var blobOnly bool + if err := v.Unmarshal(&blobOnly); err != nil { + return false + } + return blobOnly +} + func setDeleted(si *metadata.StorageItem) error { v, err := metadata.NewValue(true) if err != nil { - return errors.Wrap(err, "failed to create size value") + return errors.Wrap(err, "failed to create deleted value") } si.Update(func(b *bolt.Bucket) error { return si.SetValue(b, keyDeleted, v) @@ -45,6 +235,55 @@ func getDeleted(si *metadata.StorageItem) bool { return deleted } +func queueCommitted(si *metadata.StorageItem) error { + v, err := metadata.NewValue(true) + if err != nil { + return errors.Wrap(err, "failed to create committed value") + } + si.Queue(func(b *bolt.Bucket) error { + return si.SetValue(b, keyCommitted, v) + }) + return nil +} + +func getCommitted(si *metadata.StorageItem) bool { + v := si.Get(keyCommitted) + if v == nil { + return false + } + var committed bool + if err := v.Unmarshal(&committed); err != nil { + return false + } + return committed +} + +func queueParent(si *metadata.StorageItem, parent string) error { + if parent == "" { + return nil + } + v, err := metadata.NewValue(parent) + if err != nil { + return errors.Wrap(err, "failed to create parent value") + } + si.Update(func(b *bolt.Bucket) error { + return si.SetValue(b, keyParent, v) + }) + return nil +} + +func getParent(si *metadata.StorageItem) string { + v := si.Get(keyParent) + if v == nil { + return "" + } + var parent string + if err := v.Unmarshal(&parent); err != nil { + return "" + } + return parent +} + func setSize(si *metadata.StorageItem, s int64) error { v, err := metadata.NewValue(s) if err != nil { diff --git a/vendor/github.com/moby/buildkit/cache/migrate_v2.go b/vendor/github.com/moby/buildkit/cache/migrate_v2.go new file mode 100644 index 0000000000..9671c28258 --- /dev/null +++ b/vendor/github.com/moby/buildkit/cache/migrate_v2.go @@ -0,0 +1,257 @@ +package cache + +import ( + "context" + "io" + "os" + "time" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/leases" + "github.com/containerd/containerd/snapshots" + "github.com/moby/buildkit/cache/metadata" + "github.com/moby/buildkit/snapshot" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func migrateChainID(si *metadata.StorageItem, all map[string]*metadata.StorageItem) (digest.Digest, digest.Digest, error) { + diffID := digest.Digest(getDiffID(si)) + if diffID == "" { + return "", "", nil + } + blobID := digest.Digest(getBlob(si)) + if blobID == "" { + return "", "", nil + } + chainID := digest.Digest(getChainID(si)) + blobChainID := digest.Digest(getBlobChainID(si)) + + if chainID != "" && blobChainID != "" { + return chainID, blobChainID, nil + } + + chainID = diffID + blobChainID = digest.FromBytes([]byte(blobID + " " + diffID)) + + parent := getParent(si) + if parent != "" { + pChainID, pBlobChainID, err := migrateChainID(all[parent], all) + if err != nil { + return "", "", err + } + chainID = digest.FromBytes([]byte(pChainID + " " + chainID)) + blobChainID = digest.FromBytes([]byte(pBlobChainID + " " + blobChainID)) + } + + queueChainID(si, chainID.String()) + queueBlobChainID(si, blobChainID.String()) + + return chainID, blobChainID, si.Commit() +} + +func MigrateV2(ctx context.Context, from, to string, cs content.Store, s snapshot.Snapshotter, lm leases.Manager) error { + _, err := os.Stat(to) + if err != nil { + if !os.IsNotExist(errors.Cause(err)) { + return errors.WithStack(err) + } + } else { + return nil + } + + _, err = os.Stat(from) + if err != nil { + if !os.IsNotExist(errors.Cause(err)) { + return errors.WithStack(err) + } + return nil + } + tmpPath := to + ".tmp" + tmpFile, err := os.Create(tmpPath) + if err != nil { + return errors.WithStack(err) + } + src, err := os.Open(from) + if err != nil { + tmpFile.Close() + return errors.WithStack(err) + } + if _, err = io.Copy(tmpFile, src); err != nil { + tmpFile.Close() + src.Close() + return errors.Wrapf(err, "failed to copy db for migration") + } + src.Close() + tmpFile.Close() + + md, err := metadata.NewStore(tmpPath) + if err != nil { + return err + } + + items, err := md.All() + if err != nil { + return err + } + + byID := map[string]*metadata.StorageItem{} + for _, item := range items { + byID[item.ID()] = item + } + + // add committed, parent, snapshot + for id, item := range byID { + em := getEqualMutable(item) + if em == "" { + info, err := s.Stat(ctx, id) + if err != nil { + return err + } + if info.Kind == snapshots.KindCommitted { + queueCommitted(item) + } + if info.Parent != "" { + queueParent(item, info.Parent) + } + } else { + queueCommitted(item) + } + queueSnapshotID(item, id) + item.Commit() + } + + for _, item := range byID { + em := getEqualMutable(item) + if em != "" { + if getParent(item) == "" { + queueParent(item, getParent(byID[em])) + item.Commit() + } + } + } + + type diffPair struct { + Blobsum string + DiffID string + } + // move diffID, blobsum to new location + for _, item := range byID { + v := item.Get("blobmapping.blob") + if v == nil { + continue + } + var blob diffPair + if err := v.Unmarshal(&blob); err != nil { + return errors.WithStack(err) + } + if _, err := cs.Info(ctx, digest.Digest(blob.Blobsum)); err != nil { + continue + } + queueDiffID(item, blob.DiffID) + queueBlob(item, blob.Blobsum) + queueMediaType(item, images.MediaTypeDockerSchema2LayerGzip) + if err := item.Commit(); err != nil { + return err + } + + } + + // calculate new chainid/blobsumid + for _, item := range byID { + if _, _, err := migrateChainID(item, byID); err != nil { + return err + } + } + + ctx = context.TODO() // no cancellation allowed pass this point + + // add new leases + for _, item := range byID { + l, err := lm.Create(ctx, func(l *leases.Lease) error { + l.ID = item.ID() + l.Labels = map[string]string{ + "containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano), + } + return nil + }) + if err != nil { + // if we are running the migration twice + if errdefs.IsAlreadyExists(err) { + continue + } + return errors.Wrap(err, "failed to create lease") + } + + if err := lm.AddResource(ctx, l, leases.Resource{ + ID: getSnapshotID(item), + Type: "snapshots/" + s.Name(), + }); err != nil { + return errors.Wrapf(err, "failed to add snapshot %s to lease", item.ID()) + } + + if blobID := getBlob(item); blobID != "" { + if err := lm.AddResource(ctx, l, leases.Resource{ + ID: blobID, + Type: "content", + }); err != nil { + return errors.Wrapf(err, "failed to add blob %s to lease", item.ID()) + } + } + } + + // remove old root labels + for _, item := range byID { + if _, err := s.Update(ctx, snapshots.Info{ + Name: getSnapshotID(item), + }, "labels.containerd.io/gc.root"); err != nil { + if !errdefs.IsNotFound(errors.Cause(err)) { + return err + } + } + + if blob := getBlob(item); blob != "" { + if _, err := cs.Update(ctx, content.Info{ + Digest: digest.Digest(blob), + }, "labels.containerd.io/gc.root"); err != nil { + return err + } + } + } + + // previous implementation can leak views, just clean up all views + err = s.Walk(ctx, func(ctx context.Context, info snapshots.Info) error { + if info.Kind == snapshots.KindView { + if _, err := s.Update(ctx, snapshots.Info{ + Name: info.Name, + }, "labels.containerd.io/gc.root"); err != nil { + if !errdefs.IsNotFound(errors.Cause(err)) { + return err + } + } + } + return nil + }) + if err != nil { + return err + } + + // switch to new DB + if err := md.Close(); err != nil { + return err + } + + if err := os.Rename(tmpPath, to); err != nil { + return err + } + + for _, item := range byID { + logrus.Infof("migrated %s parent:%q snapshot:%v committed:%v blob:%v diffid:%v chainID:%v blobChainID:%v", + item.ID(), getParent(item), getSnapshotID(item), getCommitted(item), getBlob(item), getDiffID(item), getChainID(item), getBlobChainID(item)) + } + + return nil +} diff --git a/vendor/github.com/moby/buildkit/cache/refs.go b/vendor/github.com/moby/buildkit/cache/refs.go index 046f3d8fb7..89c260a6e2 100644 --- a/vendor/github.com/moby/buildkit/cache/refs.go +++ b/vendor/github.com/moby/buildkit/cache/refs.go @@ -2,15 +2,24 @@ package cache import ( "context" + "fmt" "strings" "sync" + "time" + "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/leases" "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/snapshots" "github.com/docker/docker/pkg/idtools" "github.com/moby/buildkit/cache/metadata" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/util/flightcontrol" + "github.com/moby/buildkit/util/leaseutil" + "github.com/opencontainers/go-digest" + imagespecidentity "github.com/opencontainers/image-spec/identity" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -30,6 +39,20 @@ type ImmutableRef interface { Parent() ImmutableRef Finalize(ctx context.Context, commit bool) error // Make sure reference is flushed to driver Clone() ImmutableRef + + Info() RefInfo + SetBlob(ctx context.Context, desc ocispec.Descriptor) error + Extract(ctx context.Context) error // +progress +} + +type RefInfo struct { + SnapshotID string + ChainID digest.Digest + BlobChainID digest.Digest + DiffID digest.Digest + Blob digest.Digest + MediaType string + Extracted bool } type MutableRef interface { @@ -65,6 +88,8 @@ type cacheRecord struct { // these are filled if multiple refs point to same data equalMutable *mutableRef equalImmutable *immutableRef + + parentChainCache []digest.Digest } // hold ref lock before calling @@ -81,6 +106,26 @@ func (cr *cacheRecord) mref(triggerLastUsed bool) *mutableRef { return ref } +func (cr *cacheRecord) parentChain() []digest.Digest { + if cr.parentChainCache != nil { + return cr.parentChainCache + } + blob := getBlob(cr.md) + if blob == "" { + return nil + } + + var parent []digest.Digest + if cr.parent != nil { + parent = cr.parent.parentChain() + } + pcc := make([]digest.Digest, len(parent)+1) + copy(pcc, parent) + pcc[len(parent)] = digest.Digest(blob) + cr.parentChainCache = pcc + return pcc +} + // hold ref lock before calling func (cr *cacheRecord) isDead() bool { 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) { cr.mu.Unlock() return s, nil } - driverID := cr.ID() + driverID := getSnapshotID(cr.md) if cr.equalMutable != nil { - driverID = cr.equalMutable.ID() + driverID = getSnapshotID(cr.equalMutable.md) } cr.mu.Unlock() - usage, err := cr.cm.ManagerOpt.Snapshotter.Usage(ctx, driverID) - if err != nil { - cr.mu.Lock() - isDead := cr.isDead() - cr.mu.Unlock() - if isDead { - return int64(0), nil + var usage snapshots.Usage + if !getBlobOnly(cr.md) { + var err error + usage, err = cr.cm.ManagerOpt.Snapshotter.Usage(ctx, driverID) + if err != nil { + cr.mu.Lock() + isDead := cr.isDead() + cr.mu.Unlock() + if isDead { + return int64(0), nil + } + if !errdefs.IsNotFound(err) { + return s, errors.Wrapf(err, "failed to get usage for %s", cr.ID()) + } + } + } + if dgst := getBlob(cr.md); dgst != "" { + info, err := cr.cm.ContentStore.Info(ctx, digest.Digest(dgst)) + if err == nil { + usage.Size += info.Size } - return s, errors.Wrapf(err, "failed to get usage for %s", cr.ID()) } cr.mu.Lock() setSize(cr.md, usage.Size) @@ -148,7 +205,7 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount defer cr.mu.Unlock() if cr.mutable { - m, err := cr.cm.Snapshotter.Mounts(ctx, cr.ID()) + m, err := cr.cm.Snapshotter.Mounts(ctx, getSnapshotID(cr.md)) if err != nil { return nil, errors.Wrapf(err, "failed to mount %s", cr.ID()) } @@ -159,7 +216,7 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount } if cr.equalMutable != nil && readonly { - m, err := cr.cm.Snapshotter.Mounts(ctx, cr.equalMutable.ID()) + m, err := cr.cm.Snapshotter.Mounts(ctx, getSnapshotID(cr.equalMutable.md)) if err != nil { return nil, errors.Wrapf(err, "failed to mount %s", cr.equalMutable.ID()) } @@ -170,12 +227,24 @@ func (cr *cacheRecord) Mount(ctx context.Context, readonly bool) (snapshot.Mount return nil, err } if cr.viewMount == nil { // TODO: handle this better - cr.view = identity.NewID() - m, err := cr.cm.Snapshotter.View(ctx, cr.view, cr.ID()) + view := identity.NewID() + l, err := cr.cm.LeaseManager.Create(ctx, func(l *leases.Lease) error { + l.ID = view + l.Labels = map[string]string{ + "containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano), + } + return nil + }, leaseutil.MakeTemporary) if err != nil { - cr.view = "" + return nil, err + } + ctx = leases.WithLease(ctx, l.ID) + m, err := cr.cm.Snapshotter.View(ctx, view, getSnapshotID(cr.md)) + if err != nil { + cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: l.ID}) return nil, errors.Wrapf(err, "failed to mount %s", cr.ID()) } + cr.view = view cr.viewMount = m } return cr.viewMount, nil @@ -190,7 +259,7 @@ func (cr *cacheRecord) remove(ctx context.Context, removeSnapshot bool) error { } } if removeSnapshot { - if err := cr.cm.Snapshotter.Remove(ctx, cr.ID()); err != nil { + if err := cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()}); err != nil { return errors.Wrapf(err, "failed to remove %s", cr.ID()) } } @@ -221,6 +290,134 @@ func (sr *immutableRef) Clone() ImmutableRef { return ref } +func (sr *immutableRef) Info() RefInfo { + return RefInfo{ + ChainID: digest.Digest(getChainID(sr.md)), + DiffID: digest.Digest(getDiffID(sr.md)), + Blob: digest.Digest(getBlob(sr.md)), + MediaType: getMediaType(sr.md), + BlobChainID: digest.Digest(getBlobChainID(sr.md)), + SnapshotID: getSnapshotID(sr.md), + Extracted: !getBlobOnly(sr.md), + } +} + +func (sr *immutableRef) Extract(ctx context.Context) error { + _, err := sr.sizeG.Do(ctx, sr.ID()+"-extract", func(ctx context.Context) (interface{}, error) { + snapshotID := getSnapshotID(sr.md) + if _, err := sr.cm.Snapshotter.Stat(ctx, snapshotID); err == nil { + queueBlobOnly(sr.md, false) + return nil, sr.md.Commit() + } + + parentID := "" + if sr.parent != nil { + if err := sr.parent.Extract(ctx); err != nil { + return nil, err + } + parentID = getSnapshotID(sr.parent.md) + } + info := sr.Info() + key := fmt.Sprintf("extract-%s %s", identity.NewID(), info.ChainID) + + err := sr.cm.Snapshotter.Prepare(ctx, key, parentID) + if err != nil { + return nil, err + } + + mountable, err := sr.cm.Snapshotter.Mounts(ctx, key) + if err != nil { + return nil, err + } + mounts, unmount, err := mountable.Mount() + if err != nil { + return nil, err + } + _, err = sr.cm.Applier.Apply(ctx, ocispec.Descriptor{ + Digest: info.Blob, + MediaType: info.MediaType, + }, mounts) + if err != nil { + unmount() + return nil, err + } + + if err := unmount(); err != nil { + return nil, err + } + if err := sr.cm.Snapshotter.Commit(ctx, getSnapshotID(sr.md), key); err != nil { + if !errdefs.IsAlreadyExists(err) { + return nil, err + } + } + queueBlobOnly(sr.md, false) + if err := sr.md.Commit(); err != nil { + return nil, err + } + return nil, nil + }) + return err +} + +// SetBlob associates a blob with the cache record. +// A lease must be held for the blob when calling this function +// Caller should call Info() for knowing what current values are actually set +func (sr *immutableRef) SetBlob(ctx context.Context, desc ocispec.Descriptor) error { + diffID, err := diffIDFromDescriptor(desc) + if err != nil { + return err + } + if _, err := sr.cm.ContentStore.Info(ctx, desc.Digest); err != nil { + return err + } + + sr.mu.Lock() + defer sr.mu.Unlock() + + if getChainID(sr.md) != "" { + return nil + } + + if err := sr.finalize(ctx, true); err != nil { + return err + } + + p := sr.parent + var parentChainID digest.Digest + var parentBlobChainID digest.Digest + if p != nil { + pInfo := p.Info() + if pInfo.ChainID == "" || pInfo.BlobChainID == "" { + return errors.Errorf("failed to set blob for reference with non-addressable parent") + } + parentChainID = pInfo.ChainID + parentBlobChainID = pInfo.BlobChainID + } + + if err := sr.cm.LeaseManager.AddResource(ctx, leases.Lease{ID: sr.ID()}, leases.Resource{ + ID: desc.Digest.String(), + Type: "content", + }); err != nil { + return err + } + + queueDiffID(sr.md, diffID.String()) + queueBlob(sr.md, desc.Digest.String()) + chainID := diffID + blobChainID := imagespecidentity.ChainID([]digest.Digest{desc.Digest, diffID}) + if parentChainID != "" { + chainID = imagespecidentity.ChainID([]digest.Digest{parentChainID, chainID}) + blobChainID = imagespecidentity.ChainID([]digest.Digest{parentBlobChainID, blobChainID}) + } + queueChainID(sr.md, chainID.String()) + queueBlobChainID(sr.md, blobChainID.String()) + queueMediaType(sr.md, desc.MediaType) + if err := sr.md.Commit(); err != nil { + return err + } + return nil +} + func (sr *immutableRef) Release(ctx context.Context) error { sr.cm.mu.Lock() defer sr.cm.mu.Unlock() @@ -259,8 +456,8 @@ func (sr *immutableRef) release(ctx context.Context) error { if len(sr.refs) == 0 { if sr.viewMount != nil { // TODO: release viewMount earlier if possible - if err := sr.cm.Snapshotter.Remove(ctx, sr.view); err != nil { - return errors.Wrapf(err, "failed to remove view %s", sr.view) + if err := sr.cm.LeaseManager.Delete(ctx, leases.Lease{ID: sr.view}); err != nil { + return errors.Wrapf(err, "failed to remove view lease %s", sr.view) } sr.view = "" sr.viewMount = nil @@ -269,7 +466,6 @@ func (sr *immutableRef) release(ctx context.Context) error { if sr.equalMutable != nil { sr.equalMutable.release(ctx) } - // go sr.cm.GC() } return nil @@ -298,18 +494,42 @@ func (cr *cacheRecord) finalize(ctx context.Context, commit bool) error { } return nil } - err := cr.cm.Snapshotter.Commit(ctx, cr.ID(), mutable.ID()) + + _, err := cr.cm.ManagerOpt.LeaseManager.Create(ctx, func(l *leases.Lease) error { + l.ID = cr.ID() + l.Labels = map[string]string{ + "containerd.io/gc.flat": time.Now().UTC().Format(time.RFC3339Nano), + } + return nil + }) if err != nil { + if !errdefs.IsAlreadyExists(err) { // migrator adds leases for everything + return errors.Wrap(err, "failed to create lease") + } + } + + if err := cr.cm.ManagerOpt.LeaseManager.AddResource(ctx, leases.Lease{ID: cr.ID()}, leases.Resource{ + ID: cr.ID(), + Type: "snapshots/" + cr.cm.ManagerOpt.Snapshotter.Name(), + }); err != nil { + cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()}) + return errors.Wrapf(err, "failed to add snapshot %s to lease", cr.ID()) + } + + err = cr.cm.Snapshotter.Commit(ctx, cr.ID(), mutable.ID()) + if err != nil { + cr.cm.LeaseManager.Delete(context.TODO(), leases.Lease{ID: cr.ID()}) return errors.Wrapf(err, "failed to commit %s", mutable.ID()) } mutable.dead = true go func() { cr.cm.mu.Lock() defer cr.cm.mu.Unlock() - if err := mutable.remove(context.TODO(), false); err != nil { + if err := mutable.remove(context.TODO(), true); err != nil { logrus.Error(err) } }() + cr.equalMutable = nil clearEqualMutable(cr.md) return cr.md.Commit() @@ -341,7 +561,11 @@ func (sr *mutableRef) commit(ctx context.Context) (*immutableRef, error) { } } - if err := initializeMetadata(rec); err != nil { + parentID := "" + if rec.parent != nil { + parentID = rec.parent.ID() + } + if err := initializeMetadata(rec, parentID); err != nil { return nil, err } @@ -351,6 +575,7 @@ func (sr *mutableRef) commit(ctx context.Context) (*immutableRef, error) { return nil, err } + queueCommitted(md) setSize(md, sizeUnknown) setEqualMutable(md, sr.ID()) if err := md.Commit(); err != nil { @@ -401,11 +626,6 @@ func (sr *mutableRef) release(ctx context.Context) error { return err } } - if sr.parent != nil { - if err := sr.parent.release(ctx); err != nil { - return err - } - } return sr.remove(ctx, true) } else { if sr.updateLastUsed() { diff --git a/vendor/github.com/moby/buildkit/executor/oci/resolvconf.go b/vendor/github.com/moby/buildkit/executor/oci/resolvconf.go index 3d568f7b5d..61fd36da3c 100644 --- a/vendor/github.com/moby/buildkit/executor/oci/resolvconf.go +++ b/vendor/github.com/moby/buildkit/executor/oci/resolvconf.go @@ -16,6 +16,9 @@ var g flightcontrol.Group var notFirstRun bool var lastNotEmpty bool +// overridden by tests +var resolvconfGet = resolvconf.Get + type DNSConfig struct { Nameservers []string Options []string @@ -59,7 +62,7 @@ func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.Identity } var dt []byte - f, err := resolvconf.Get() + f, err := resolvconfGet() if err != nil { if !os.IsNotExist(err) { return "", err @@ -88,14 +91,12 @@ func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.Identity if err != nil { return "", err } - } else { - // Logic seems odd here: why are we filtering localhost IPs - // only if neither of the DNS configs were specified? - // Logic comes from https://github.com/docker/libnetwork/blob/164a77ee6d24fb2b1d61f8ad3403a51d8453899e/sandbox_dns_unix.go#L230-L269 - f, err = resolvconf.FilterResolvDNS(f.Content, true) - if err != nil { - return "", err - } + dt = f.Content + } + + f, err = resolvconf.FilterResolvDNS(dt, true) + if err != nil { + return "", err } tmpPath := p + ".tmp" diff --git a/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go b/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go index 840520e8cc..03b02e3dd6 100644 --- a/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go +++ b/vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "math" "net/url" "path" "path/filepath" @@ -1325,7 +1326,7 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform out += ds.stageName + " " } ds.cmdIndex++ - out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal) + out += fmt.Sprintf("%*d/%d] ", int(1+math.Log10(float64(ds.cmdTotal))), ds.cmdIndex, ds.cmdTotal) return out + str } diff --git a/vendor/github.com/moby/buildkit/go.mod b/vendor/github.com/moby/buildkit/go.mod index c2b385b359..637b15b902 100644 --- a/vendor/github.com/moby/buildkit/go.mod +++ b/vendor/github.com/moby/buildkit/go.mod @@ -9,7 +9,7 @@ require ( github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58 // indirect github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601 // indirect github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50 - github.com/containerd/containerd v1.3.0 + github.com/containerd/containerd v1.4.0-0.20191014053712-acdcf13d5eaf github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c // indirect github.com/containerd/go-cni v0.0.0-20190813230227-49fbd9b210f3 diff --git a/vendor/github.com/moby/buildkit/snapshot/blobmapping/snapshotter.go b/vendor/github.com/moby/buildkit/snapshot/blobmapping/snapshotter.go deleted file mode 100644 index b4a16e29c7..0000000000 --- a/vendor/github.com/moby/buildkit/snapshot/blobmapping/snapshotter.go +++ /dev/null @@ -1,151 +0,0 @@ -package blobmapping - -import ( - "context" - "time" - - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/snapshots" - "github.com/moby/buildkit/cache/metadata" - "github.com/moby/buildkit/snapshot" - digest "github.com/opencontainers/go-digest" - "github.com/sirupsen/logrus" - bolt "go.etcd.io/bbolt" -) - -const blobKey = "blobmapping.blob" - -type Opt struct { - Content content.Store - Snapshotter snapshot.SnapshotterBase - MetadataStore *metadata.Store -} - -type Info struct { - snapshots.Info - Blob string -} - -type DiffPair struct { - Blobsum digest.Digest - DiffID digest.Digest -} - -// this snapshotter keeps an internal mapping between a snapshot and a blob - -type Snapshotter struct { - snapshot.SnapshotterBase - opt Opt -} - -func NewSnapshotter(opt Opt) snapshot.Snapshotter { - s := &Snapshotter{ - SnapshotterBase: opt.Snapshotter, - opt: opt, - } - - return s -} - -// Remove also removes a reference to a blob. If it is a last reference then it deletes it the blob as well -// Remove is not safe to be called concurrently -func (s *Snapshotter) Remove(ctx context.Context, key string) error { - _, blob, err := s.GetBlob(ctx, key) - if err != nil { - return err - } - - blobs, err := s.opt.MetadataStore.Search(index(blob)) - if err != nil { - return err - } - - if err := s.SnapshotterBase.Remove(ctx, key); err != nil { - return err - } - - if len(blobs) == 1 && blobs[0].ID() == key { // last snapshot - if err := s.opt.Content.Delete(ctx, blob); err != nil { - logrus.Errorf("failed to delete blob %v: %+v", blob, err) - } - } - return nil -} - -func (s *Snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { - u, err := s.SnapshotterBase.Usage(ctx, key) - if err != nil { - return snapshots.Usage{}, err - } - _, blob, err := s.GetBlob(ctx, key) - if err != nil { - return u, err - } - if blob != "" { - info, err := s.opt.Content.Info(ctx, blob) - if err != nil { - return u, err - } - (&u).Add(snapshots.Usage{Size: info.Size, Inodes: 1}) - } - return u, nil -} - -func (s *Snapshotter) GetBlob(ctx context.Context, key string) (digest.Digest, digest.Digest, error) { - md, _ := s.opt.MetadataStore.Get(key) - v := md.Get(blobKey) - if v == nil { - return "", "", nil - } - var blob DiffPair - if err := v.Unmarshal(&blob); err != nil { - return "", "", err - } - return blob.DiffID, blob.Blobsum, nil -} - -// Validates that there is no blob associated with the snapshot. -// Checks that there is a blob in the content store. -// If same blob has already been set then this is a noop. -func (s *Snapshotter) SetBlob(ctx context.Context, key string, diffID, blobsum digest.Digest) error { - info, err := s.opt.Content.Info(ctx, blobsum) - if err != nil { - return err - } - if _, ok := info.Labels["containerd.io/uncompressed"]; !ok { - labels := map[string]string{ - "containerd.io/uncompressed": diffID.String(), - } - if _, err := s.opt.Content.Update(ctx, content.Info{ - Digest: blobsum, - Labels: labels, - }, "labels.containerd.io/uncompressed"); err != nil { - return err - } - } - // update gc.root cause blob might be held by lease only - if _, err := s.opt.Content.Update(ctx, content.Info{ - Digest: blobsum, - Labels: map[string]string{ - "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339Nano), - }, - }, "labels.containerd.io/gc.root"); err != nil { - return err - } - - md, _ := s.opt.MetadataStore.Get(key) - - v, err := metadata.NewValue(DiffPair{DiffID: diffID, Blobsum: blobsum}) - if err != nil { - return err - } - v.Index = index(blobsum) - - return md.Update(func(b *bolt.Bucket) error { - return md.SetValue(b, blobKey, v) - }) -} - -func index(blob digest.Digest) string { - return "blobmap::" + blob.String() -} diff --git a/vendor/github.com/moby/buildkit/snapshot/containerd/content.go b/vendor/github.com/moby/buildkit/snapshot/containerd/content.go new file mode 100644 index 0000000000..8e42d4257e --- /dev/null +++ b/vendor/github.com/moby/buildkit/snapshot/containerd/content.go @@ -0,0 +1,82 @@ +package containerd + +import ( + "context" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/namespaces" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +func NewContentStore(store content.Store, ns string) content.Store { + return &nsContent{ns, store} +} + +type nsContent struct { + ns string + content.Store +} + +func (c *nsContent) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.Info(ctx, dgst) +} + +func (c *nsContent) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.Update(ctx, info, fieldpaths...) +} + +func (c *nsContent) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.Walk(ctx, fn, filters...) +} + +func (c *nsContent) Delete(ctx context.Context, dgst digest.Digest) error { + return errors.Errorf("contentstore.Delete usage is forbidden") +} + +func (c *nsContent) Status(ctx context.Context, ref string) (content.Status, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.Status(ctx, ref) +} + +func (c *nsContent) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.ListStatuses(ctx, filters...) +} + +func (c *nsContent) Abort(ctx context.Context, ref string) error { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.Abort(ctx, ref) +} + +func (c *nsContent) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + return c.Store.ReaderAt(ctx, desc) +} + +func (c *nsContent) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { + return c.writer(ctx, 3, opts...) +} + +func (c *nsContent) writer(ctx context.Context, retries int, opts ...content.WriterOpt) (content.Writer, error) { + ctx = namespaces.WithNamespace(ctx, c.ns) + w, err := c.Store.Writer(ctx, opts...) + if err != nil { + return nil, err + } + return &nsWriter{Writer: w, ns: c.ns}, nil +} + +type nsWriter struct { + content.Writer + ns string +} + +func (w *nsWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { + ctx = namespaces.WithNamespace(ctx, w.ns) + return w.Writer.Commit(ctx, size, expected, opts...) +} diff --git a/vendor/github.com/moby/buildkit/snapshot/containerd/snapshotter.go b/vendor/github.com/moby/buildkit/snapshot/containerd/snapshotter.go new file mode 100644 index 0000000000..e38e725238 --- /dev/null +++ b/vendor/github.com/moby/buildkit/snapshot/containerd/snapshotter.go @@ -0,0 +1,63 @@ +package containerd + +import ( + "context" + + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/snapshots" + "github.com/docker/docker/pkg/idtools" + "github.com/moby/buildkit/snapshot" + "github.com/pkg/errors" +) + +func NewSnapshotter(name string, snapshotter snapshots.Snapshotter, ns string, idmap *idtools.IdentityMapping) snapshot.Snapshotter { + return snapshot.FromContainerdSnapshotter(name, &nsSnapshotter{ns, snapshotter}, idmap) +} + +func NSSnapshotter(ns string, snapshotter snapshots.Snapshotter) snapshots.Snapshotter { + return &nsSnapshotter{ns: ns, Snapshotter: snapshotter} +} + +type nsSnapshotter struct { + ns string + snapshots.Snapshotter +} + +func (s *nsSnapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Stat(ctx, key) +} + +func (s *nsSnapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Update(ctx, info, fieldpaths...) +} + +func (s *nsSnapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Usage(ctx, key) +} +func (s *nsSnapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Mounts(ctx, key) +} +func (s *nsSnapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Prepare(ctx, key, parent, opts...) +} +func (s *nsSnapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.View(ctx, key, parent, opts...) +} +func (s *nsSnapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Commit(ctx, name, key, opts...) +} +func (s *nsSnapshotter) Remove(ctx context.Context, key string) error { + return errors.Errorf("calling snapshotter.Remove is forbidden") +} +func (s *nsSnapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { + ctx = namespaces.WithNamespace(ctx, s.ns) + return s.Snapshotter.Walk(ctx, fn) +} diff --git a/vendor/github.com/moby/buildkit/snapshot/snapshotter.go b/vendor/github.com/moby/buildkit/snapshot/snapshotter.go index 4e930e9013..f45ced4e72 100644 --- a/vendor/github.com/moby/buildkit/snapshot/snapshotter.go +++ b/vendor/github.com/moby/buildkit/snapshot/snapshotter.go @@ -9,7 +9,6 @@ import ( "github.com/containerd/containerd/mount" "github.com/containerd/containerd/snapshots" "github.com/docker/docker/pkg/idtools" - digest "github.com/opencontainers/go-digest" ) type Mountable interface { @@ -18,7 +17,8 @@ type Mountable interface { IdentityMapping() *idtools.IdentityMapping } -type SnapshotterBase interface { +// Snapshotter defines interface that any snapshot implementation should satisfy +type Snapshotter interface { Name() string Mounts(ctx context.Context, key string) (Mountable, error) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) error @@ -34,18 +34,7 @@ type SnapshotterBase interface { IdentityMapping() *idtools.IdentityMapping } -// Snapshotter defines interface that any snapshot implementation should satisfy -type Snapshotter interface { - Blobmapper - SnapshotterBase -} - -type Blobmapper interface { - GetBlob(ctx context.Context, key string) (digest.Digest, digest.Digest, error) - SetBlob(ctx context.Context, key string, diffID, blob digest.Digest) error -} - -func FromContainerdSnapshotter(name string, s snapshots.Snapshotter, idmap *idtools.IdentityMapping) SnapshotterBase { +func FromContainerdSnapshotter(name string, s snapshots.Snapshotter, idmap *idtools.IdentityMapping) Snapshotter { return &fromContainerd{name: name, Snapshotter: s, idmap: idmap} } diff --git a/vendor/github.com/moby/buildkit/util/imageutil/config.go b/vendor/github.com/moby/buildkit/util/imageutil/config.go index 07a5846677..9568864a55 100644 --- a/vendor/github.com/moby/buildkit/util/imageutil/config.go +++ b/vendor/github.com/moby/buildkit/util/imageutil/config.go @@ -3,7 +3,6 @@ package imageutil import ( "context" "encoding/json" - "fmt" "sync" "time" @@ -50,7 +49,7 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co } if leaseManager != nil { - ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute)) + ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute), leaseutil.MakeTemporary) if err != nil { return "", nil, errors.WithStack(err) } @@ -94,12 +93,9 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co } children := childrenConfigHandler(cache, platform) - if m, ok := cache.(content.Manager); ok { - children = SetChildrenLabelsNonBlobs(m, children) - } handlers := []images.Handler{ - fetchWithoutRoot(remotes.FetchHandler(cache, fetcher)), + remotes.FetchHandler(cache, fetcher), children, } 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 return desc.Digest, dt, nil } -func fetchWithoutRoot(fetch images.HandlerFunc) images.HandlerFunc { - return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) { - if desc.Annotations == nil { - desc.Annotations = map[string]string{} - } - desc.Annotations["buildkit/noroot"] = "true" - return fetch(ctx, desc) - } -} - func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc { return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) { var descs []specs.Descriptor @@ -207,39 +193,3 @@ func DetectManifestBlobMediaType(dt []byte) (string, error) { } return images.MediaTypeDockerSchema2ManifestList, nil } - -func SetChildrenLabelsNonBlobs(manager content.Manager, f images.HandlerFunc) images.HandlerFunc { - return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) { - children, err := f(ctx, desc) - if err != nil { - return children, err - } - - if len(children) > 0 { - info := content.Info{ - Digest: desc.Digest, - Labels: map[string]string{}, - } - fields := []string{} - for i, ch := range children { - switch ch.MediaType { - case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip, specs.MediaTypeImageLayer, specs.MediaTypeImageLayerGzip: - continue - default: - } - - info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String() - fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i)) - } - - if len(info.Labels) > 0 { - _, err := manager.Update(ctx, info, fields...) - if err != nil { - return nil, err - } - } - } - - return children, err - } -} diff --git a/vendor/github.com/moby/buildkit/util/leaseutil/manager.go b/vendor/github.com/moby/buildkit/util/leaseutil/manager.go index fe363e0a02..45a35273a5 100644 --- a/vendor/github.com/moby/buildkit/util/leaseutil/manager.go +++ b/vendor/github.com/moby/buildkit/util/leaseutil/manager.go @@ -27,26 +27,49 @@ func WithLease(ctx context.Context, ls leases.Manager, opts ...leases.Opt) (cont }, nil } +func MakeTemporary(l *leases.Lease) error { + if l.Labels == nil { + l.Labels = map[string]string{} + } + l.Labels["buildkit/lease.temporary"] = time.Now().UTC().Format(time.RFC3339Nano) + return nil +} + func WithNamespace(lm leases.Manager, ns string) leases.Manager { - return &nsLM{Manager: lm, ns: ns} + return &nsLM{manager: lm, ns: ns} } type nsLM struct { - leases.Manager - ns string + manager leases.Manager + ns string } func (l *nsLM) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) { ctx = namespaces.WithNamespace(ctx, l.ns) - return l.Manager.Create(ctx, opts...) + return l.manager.Create(ctx, opts...) } func (l *nsLM) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error { ctx = namespaces.WithNamespace(ctx, l.ns) - return l.Manager.Delete(ctx, lease, opts...) + return l.manager.Delete(ctx, lease, opts...) } func (l *nsLM) List(ctx context.Context, filters ...string) ([]leases.Lease, error) { ctx = namespaces.WithNamespace(ctx, l.ns) - return l.Manager.List(ctx, filters...) + return l.manager.List(ctx, filters...) +} + +func (l *nsLM) AddResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { + ctx = namespaces.WithNamespace(ctx, l.ns) + return l.manager.AddResource(ctx, lease, resource) +} + +func (l *nsLM) DeleteResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { + ctx = namespaces.WithNamespace(ctx, l.ns) + return l.manager.DeleteResource(ctx, lease, resource) +} + +func (l *nsLM) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) { + ctx = namespaces.WithNamespace(ctx, l.ns) + return l.manager.ListResources(ctx, lease) } From fe16d95dcd5c5332b55054f2d7aaac08ea9f795f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Wed, 9 Oct 2019 17:20:17 -0700 Subject: [PATCH 2/5] builder-next: update adapters to new buildkit interfaces Signed-off-by: Tonis Tiigi --- .../adapters/containerimage/pull.go | 32 +++++++++++++++--- .../adapters/snapshot/snapshot.go | 14 ++++---- builder/builder-next/builder.go | 9 ----- builder/builder-next/controller.go | 33 ++++++++++++------- .../builder-next/imagerefchecker/checker.go | 4 ++- builder/builder-next/worker/worker.go | 19 ++++++++++- 6 files changed, 75 insertions(+), 36 deletions(-) diff --git a/builder/builder-next/adapters/containerimage/pull.go b/builder/builder-next/adapters/containerimage/pull.go index 43b84919c7..d3d2693264 100644 --- a/builder/builder-next/adapters/containerimage/pull.go +++ b/builder/builder-next/adapters/containerimage/pull.go @@ -53,6 +53,7 @@ type SourceOpt struct { MetadataStore metadata.V2MetadataService ImageStore image.Store ResolverOpt resolver.ResolveOptionsFunc + LayerStore layer.Store } type imageSource struct { @@ -360,6 +361,23 @@ func (p *puller) CacheKey(ctx context.Context, index int) (string, bool, error) return k, true, nil } +func (p *puller) getRef(ctx context.Context, diffIDs []layer.DiffID, opts ...cache.RefOption) (cache.ImmutableRef, error) { + var parent cache.ImmutableRef + if len(diffIDs) > 1 { + var err error + parent, err = p.getRef(ctx, diffIDs[:len(diffIDs)-1], opts...) + if err != nil { + return nil, err + } + defer parent.Release(context.TODO()) + } + return p.is.CacheAccessor.GetByBlob(ctx, ocispec.Descriptor{ + Annotations: map[string]string{ + "containerd.io/uncompressed": diffIDs[len(diffIDs)-1].String(), + }, + }, parent, opts...) +} + func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) { p.resolveLocal() if err := p.resolve(ctx); err != nil { @@ -372,11 +390,15 @@ func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) { if len(img.RootFS.DiffIDs) == 0 { return nil, nil } - ref, err := p.is.CacheAccessor.GetFromSnapshotter(ctx, string(img.RootFS.ChainID()), cache.WithDescription(fmt.Sprintf("from local %s", p.ref))) - if err != nil { - return nil, err + l, err := p.is.LayerStore.Get(img.RootFS.ChainID()) + if err == nil { + layer.ReleaseAndLog(p.is.LayerStore, l) + ref, err := p.getRef(ctx, img.RootFS.DiffIDs, cache.WithDescription(fmt.Sprintf("from local %s", p.ref))) + if err != nil { + return nil, err + } + return ref, nil } - return ref, nil } } @@ -550,7 +572,7 @@ func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) { return nil, err } - ref, err := p.is.CacheAccessor.GetFromSnapshotter(ctx, string(rootFS.ChainID()), cache.WithDescription(fmt.Sprintf("pulled from %s", p.ref))) + ref, err := p.getRef(ctx, rootFS.DiffIDs, cache.WithDescription(fmt.Sprintf("pulled from %s", p.ref))) release() if err != nil { return nil, err diff --git a/builder/builder-next/adapters/snapshot/snapshot.go b/builder/builder-next/adapters/snapshot/snapshot.go index 2bd4276198..2714004ff1 100644 --- a/builder/builder-next/adapters/snapshot/snapshot.go +++ b/builder/builder-next/adapters/snapshot/snapshot.go @@ -51,10 +51,8 @@ type snapshotter struct { reg graphIDRegistrar } -var _ snapshot.SnapshotterBase = &snapshotter{} - // NewSnapshotter creates a new snapshotter -func NewSnapshotter(opt Opt) (snapshot.SnapshotterBase, error) { +func NewSnapshotter(opt Opt) (snapshot.Snapshotter, error) { dbPath := filepath.Join(opt.Root, "snapshots.db") db, err := bolt.Open(dbPath, 0600, nil) if err != nil { @@ -87,11 +85,11 @@ func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...s origParent := parent if parent != "" { if l, err := s.getLayer(parent, false); err != nil { - return err + return errors.Wrapf(err, "failed to get parent layer %s", parent) } else if l != nil { parent, err = getGraphID(l) if err != nil { - return err + return errors.Wrapf(err, "failed to get parent graphid %s", l.ChainID()) } } else { parent, _ = s.getGraphDriverID(parent) @@ -146,7 +144,7 @@ func (s *snapshotter) getLayer(key string, withCommitted bool) (layer.Layer, err return nil }); err != nil { s.mu.Unlock() - return nil, err + return nil, errors.WithStack(err) } if id == "" { s.mu.Unlock() @@ -157,12 +155,12 @@ func (s *snapshotter) getLayer(key string, withCommitted bool) (layer.Layer, err l, err = s.opt.LayerStore.Get(id) if err != nil { s.mu.Unlock() - return nil, err + return nil, errors.WithStack(err) } s.refs[key] = l if err := s.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(key)) - return err + return errors.WithStack(err) }); err != nil { s.mu.Unlock() return nil, err diff --git a/builder/builder-next/builder.go b/builder/builder-next/builder.go index f2e111ff48..b07e88e200 100644 --- a/builder/builder-next/builder.go +++ b/builder/builder-next/builder.go @@ -10,7 +10,6 @@ import ( "sync" "time" - "github.com/containerd/containerd/content" "github.com/containerd/containerd/platforms" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/backend" @@ -468,14 +467,6 @@ func (sp *pruneProxy) SendMsg(m interface{}) error { return nil } -type contentStoreNoLabels struct { - content.Store -} - -func (c *contentStoreNoLabels) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) { - return content.Info{}, nil -} - type wrapRC struct { io.ReadCloser once sync.Once diff --git a/builder/builder-next/controller.go b/builder/builder-next/controller.go index 17acac505e..7b45d8a9ee 100644 --- a/builder/builder-next/controller.go +++ b/builder/builder-next/controller.go @@ -6,7 +6,9 @@ import ( "path/filepath" "github.com/containerd/containerd/content/local" + ctdmetadata "github.com/containerd/containerd/metadata" "github.com/containerd/containerd/platforms" + "github.com/containerd/containerd/snapshots" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/builder/builder-next/adapters/containerimage" @@ -29,13 +31,15 @@ import ( dockerfile "github.com/moby/buildkit/frontend/dockerfile/builder" "github.com/moby/buildkit/frontend/gateway" "github.com/moby/buildkit/frontend/gateway/forwarder" - "github.com/moby/buildkit/snapshot/blobmapping" + containerdsnapshot "github.com/moby/buildkit/snapshot/containerd" "github.com/moby/buildkit/solver/bboltcachestorage" "github.com/moby/buildkit/util/binfmt_misc" "github.com/moby/buildkit/util/entitlements" + "github.com/moby/buildkit/util/leaseutil" "github.com/moby/buildkit/worker" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + bolt "go.etcd.io/bbolt" ) func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { @@ -55,7 +59,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { return nil, errors.Errorf("could not access graphdriver") } - sbase, err := snapshot.NewSnapshotter(snapshot.Opt{ + snapshotter, err := snapshot.NewSnapshotter(snapshot.Opt{ GraphDriver: driver, LayerStore: dist.LayerStore, Root: root, @@ -69,20 +73,22 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { if err != nil { return nil, err } - store = &contentStoreNoLabels{store} + + db, err := bolt.Open(filepath.Join(root, "containerdmeta.db"), 0644, nil) + if err != nil { + return nil, errors.WithStack(err) + } + + mdb := ctdmetadata.NewDB(db, store, map[string]snapshots.Snapshotter{}) + + store = containerdsnapshot.NewContentStore(mdb.ContentStore(), "buildkit") md, err := metadata.NewStore(filepath.Join(root, "metadata.db")) if err != nil { return nil, err } - snapshotter := blobmapping.NewSnapshotter(blobmapping.Opt{ - Content: store, - Snapshotter: sbase, - MetadataStore: md, - }) - - layerGetter, ok := sbase.(imagerefchecker.LayerGetter) + layerGetter, ok := snapshotter.(imagerefchecker.LayerGetter) if !ok { return nil, errors.Errorf("snapshotter does not implement layergetter") } @@ -96,6 +102,8 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { Snapshotter: snapshotter, MetadataStore: md, PruneRefChecker: refChecker, + LeaseManager: leaseutil.WithNamespace(ctdmetadata.NewLeaseManager(mdb), "buildkit"), + ContentStore: store, }) if err != nil { return nil, err @@ -109,6 +117,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { ImageStore: dist.ImageStore, ReferenceStore: dist.ReferenceStore, ResolverOpt: opt.ResolverOpt, + LayerStore: dist.LayerStore, }) if err != nil { return nil, err @@ -121,7 +130,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { return nil, err } - differ, ok := sbase.(containerimageexp.Differ) + differ, ok := snapshotter.(containerimageexp.Differ) if !ok { return nil, errors.Errorf("snapshotter doesn't support differ") } @@ -145,7 +154,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { return nil, errors.Wrap(err, "could not get builder GC policy") } - layers, ok := sbase.(mobyworker.LayerAccess) + layers, ok := snapshotter.(mobyworker.LayerAccess) if !ok { return nil, errors.Errorf("snapshotter doesn't support differ") } diff --git a/builder/builder-next/imagerefchecker/checker.go b/builder/builder-next/imagerefchecker/checker.go index 052391d589..dde61f4c83 100644 --- a/builder/builder-next/imagerefchecker/checker.go +++ b/builder/builder-next/imagerefchecker/checker.go @@ -6,6 +6,7 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/layer" "github.com/moby/buildkit/cache" + digest "github.com/opencontainers/go-digest" ) // LayerGetter abstracts away the snapshotter @@ -57,7 +58,8 @@ type checker struct { cache map[string]bool } -func (c *checker) Exists(key string) bool { +func (c *checker) Exists(key string, chain []digest.Digest) bool { + // TODO: neeeds update if c.opt.ImageStore == nil { return false } diff --git a/builder/builder-next/worker/worker.go b/builder/builder-next/worker/worker.go index b823c9abf2..1ecdbdb390 100644 --- a/builder/builder-next/worker/worker.go +++ b/builder/builder-next/worker/worker.go @@ -305,6 +305,23 @@ func (w *Worker) PruneCacheMounts(ctx context.Context, ids []string) error { return nil } +func (w *Worker) getRef(ctx context.Context, diffIDs []layer.DiffID, opts ...cache.RefOption) (cache.ImmutableRef, error) { + var parent cache.ImmutableRef + if len(diffIDs) > 1 { + var err error + parent, err = w.getRef(ctx, diffIDs[:len(diffIDs)-1], opts...) + if err != nil { + return nil, err + } + defer parent.Release(context.TODO()) + } + return w.CacheManager.GetByBlob(context.TODO(), ocispec.Descriptor{ + Annotations: map[string]string{ + "containerd.io/uncompressed": diffIDs[len(diffIDs)-1].String(), + }, + }, parent, opts...) +} + // FromRemote converts a remote snapshot reference to a local one func (w *Worker) FromRemote(ctx context.Context, remote *solver.Remote) (cache.ImmutableRef, error) { rootfs, err := getLayers(ctx, remote.Descriptors) @@ -353,7 +370,7 @@ func (w *Worker) FromRemote(ctx context.Context, remote *solver.Remote) (cache.I if v, ok := remote.Descriptors[i].Annotations["buildkit/description"]; ok { descr = v } - ref, err := w.CacheManager.GetFromSnapshotter(ctx, string(layer.CreateChainID(rootFS.DiffIDs[:i+1])), cache.WithDescription(descr), cache.WithCreationTime(tm)) + ref, err := w.getRef(ctx, rootFS.DiffIDs[:i+1], cache.WithDescription(descr), cache.WithCreationTime(tm)) if err != nil { return nil, err } From f14c9d4df5f572745aee16ad55c385b5d7712de8 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Thu, 10 Oct 2019 15:13:11 -0700 Subject: [PATCH 3/5] builder-next: track layers and graphdrivers with leases Signed-off-by: Tonis Tiigi --- .../adapters/snapshot/leasemanager.go | 123 ++++++++++++++++++ .../adapters/snapshot/snapshot.go | 62 ++++++++- builder/builder-next/controller.go | 24 ++-- 3 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 builder/builder-next/adapters/snapshot/leasemanager.go diff --git a/builder/builder-next/adapters/snapshot/leasemanager.go b/builder/builder-next/adapters/snapshot/leasemanager.go new file mode 100644 index 0000000000..ed6039eb25 --- /dev/null +++ b/builder/builder-next/adapters/snapshot/leasemanager.go @@ -0,0 +1,123 @@ +package snapshot + +import ( + "context" + "sync" + + "github.com/containerd/containerd/leases" + "github.com/sirupsen/logrus" +) + +type sLM struct { + manager leases.Manager + s *snapshotter + + mu sync.Mutex + byLease map[string]map[string]struct{} + bySnapshot map[string]map[string]struct{} +} + +func newLeaseManager(s *snapshotter, lm leases.Manager) *sLM { + return &sLM{ + s: s, + manager: lm, + + byLease: map[string]map[string]struct{}{}, + bySnapshot: map[string]map[string]struct{}{}, + } +} + +func (l *sLM) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) { + return l.manager.Create(ctx, opts...) +} + +func (l *sLM) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error { + if err := l.manager.Delete(ctx, lease, opts...); err != nil { + return err + } + l.mu.Lock() + if snaps, ok := l.byLease[lease.ID]; ok { + for sID := range snaps { + l.delRef(lease.ID, sID) + } + } + l.mu.Unlock() + return nil +} + +func (l *sLM) List(ctx context.Context, filters ...string) ([]leases.Lease, error) { + return l.manager.List(ctx, filters...) +} + +func (l *sLM) AddResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { + if err := l.manager.AddResource(ctx, lease, resource); err != nil { + return err + } + if resource.Type == "snapshots/default" { + l.mu.Lock() + l.addRef(lease.ID, resource.ID) + l.mu.Unlock() + } + return nil +} + +func (l *sLM) DeleteResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error { + if err := l.manager.DeleteResource(ctx, lease, resource); err != nil { + return err + } + if resource.Type == "snapshots/default" { + l.mu.Lock() + l.delRef(lease.ID, resource.ID) + l.mu.Unlock() + } + return nil +} + +func (l *sLM) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) { + return l.manager.ListResources(ctx, lease) +} + +func (l *sLM) addRef(lID, sID string) { + load := false + snapshots, ok := l.byLease[lID] + if !ok { + snapshots = map[string]struct{}{} + l.byLease[lID] = snapshots + } + if _, ok := snapshots[sID]; !ok { + snapshots[sID] = struct{}{} + } + leases, ok := l.bySnapshot[sID] + if !ok { + leases = map[string]struct{}{} + l.byLease[sID] = leases + load = true + } + if _, ok := leases[lID]; !ok { + leases[lID] = struct{}{} + } + + if load { + l.s.getLayer(sID, true) + } +} + +func (l *sLM) delRef(lID, sID string) { + snapshots, ok := l.byLease[lID] + if !ok { + delete(snapshots, sID) + if len(snapshots) == 0 { + delete(l.byLease, lID) + } + } + leases, ok := l.bySnapshot[sID] + if !ok { + delete(leases, lID) + if len(leases) == 0 { + delete(l.bySnapshot, sID) + if err := l.s.remove(context.TODO(), sID); err != nil { + logrus.Warnf("failed to remove snapshot %v", sID) + } + } + } +} diff --git a/builder/builder-next/adapters/snapshot/snapshot.go b/builder/builder-next/adapters/snapshot/snapshot.go index 2714004ff1..ff05b5632d 100644 --- a/builder/builder-next/adapters/snapshot/snapshot.go +++ b/builder/builder-next/adapters/snapshot/snapshot.go @@ -7,6 +7,7 @@ import ( "strings" "sync" + "github.com/containerd/containerd/leases" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/snapshots" "github.com/docker/docker/daemon/graphdriver" @@ -21,6 +22,7 @@ import ( var keyParent = []byte("parent") var keyCommitted = []byte("committed") +var keyIsCommitted = []byte("iscommitted") var keyChainID = []byte("chainid") var keySize = []byte("size") @@ -52,16 +54,16 @@ type snapshotter struct { } // NewSnapshotter creates a new snapshotter -func NewSnapshotter(opt Opt) (snapshot.Snapshotter, error) { +func NewSnapshotter(opt Opt, prevLM leases.Manager) (snapshot.Snapshotter, leases.Manager, error) { dbPath := filepath.Join(opt.Root, "snapshots.db") db, err := bolt.Open(dbPath, 0600, nil) if err != nil { - return nil, errors.Wrapf(err, "failed to open database file %s", dbPath) + return nil, nil, errors.Wrapf(err, "failed to open database file %s", dbPath) } reg, ok := opt.LayerStore.(graphIDRegistrar) if !ok { - return nil, errors.Errorf("layerstore doesn't support graphID registration") + return nil, nil, errors.Errorf("layerstore doesn't support graphID registration") } s := &snapshotter{ @@ -70,7 +72,28 @@ func NewSnapshotter(opt Opt) (snapshot.Snapshotter, error) { refs: map[string]layer.Layer{}, reg: reg, } - return s, nil + + lm := newLeaseManager(s, prevLM) + + // TODO: temp-leases + + ll, err := lm.List(context.TODO()) + if err != nil { + return nil, nil, err + } + for _, l := range ll { + rr, err := lm.ListResources(context.TODO(), l) + if err != nil { + return nil, nil, err + } + for _, r := range rr { + if r.Type == "snapshots/default" { + lm.addRef(l.ID, r.ID) + } + } + } + + return s, lm, nil } func (s *snapshotter) Name() string { @@ -295,6 +318,10 @@ func (s *snapshotter) Mounts(ctx context.Context, key string) (snapshot.Mountabl } func (s *snapshotter) Remove(ctx context.Context, key string) error { + return errors.Errorf("calling snapshot.remove is forbidden") +} + +func (s *snapshotter) remove(ctx context.Context, key string) error { l, err := s.getLayer(key, true) if err != nil { return err @@ -303,8 +330,17 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error { id, _ := s.getGraphDriverID(key) var found bool + var alreadyCommitted bool if err := s.db.Update(func(tx *bolt.Tx) error { - found = tx.Bucket([]byte(key)) != nil + b := tx.Bucket([]byte(key)) + found = b != nil + + if b != nil { + if b.Get(keyIsCommitted) != nil { + alreadyCommitted = true + return nil + } + } if found { tx.DeleteBucket([]byte(key)) if id != key { @@ -316,6 +352,10 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error { return err } + if alreadyCommitted { + return nil + } + if l != nil { s.mu.Lock() delete(s.refs, key) @@ -337,7 +377,17 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap if err != nil { return err } - return b.Put(keyCommitted, []byte(key)) + if err := b.Put(keyCommitted, []byte(key)); err != nil { + return err + } + b, err = tx.CreateBucketIfNotExists([]byte(key)) + if err != nil { + return err + } + if err := b.Put(keyIsCommitted, []byte{}); err != nil { + return err + } + return nil }) } diff --git a/builder/builder-next/controller.go b/builder/builder-next/controller.go index 7b45d8a9ee..e42c128e92 100644 --- a/builder/builder-next/controller.go +++ b/builder/builder-next/controller.go @@ -59,16 +59,6 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { return nil, errors.Errorf("could not access graphdriver") } - snapshotter, err := snapshot.NewSnapshotter(snapshot.Opt{ - GraphDriver: driver, - LayerStore: dist.LayerStore, - Root: root, - IdentityMapping: opt.IdentityMapping, - }) - if err != nil { - return nil, err - } - store, err := local.NewStore(filepath.Join(root, "content")) if err != nil { return nil, err @@ -83,6 +73,18 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { store = containerdsnapshot.NewContentStore(mdb.ContentStore(), "buildkit") + lm := leaseutil.WithNamespace(ctdmetadata.NewLeaseManager(mdb), "buildkit") + + snapshotter, lm, err := snapshot.NewSnapshotter(snapshot.Opt{ + GraphDriver: driver, + LayerStore: dist.LayerStore, + Root: root, + IdentityMapping: opt.IdentityMapping, + }, lm) + if err != nil { + return nil, err + } + md, err := metadata.NewStore(filepath.Join(root, "metadata.db")) if err != nil { return nil, err @@ -102,7 +104,7 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { Snapshotter: snapshotter, MetadataStore: md, PruneRefChecker: refChecker, - LeaseManager: leaseutil.WithNamespace(ctdmetadata.NewLeaseManager(mdb), "buildkit"), + LeaseManager: lm, ContentStore: store, }) if err != nil { From f632e2d8d3f9fe11e9bb04d7df1ba3a510d8d648 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 21 Oct 2019 22:57:08 -0700 Subject: [PATCH 4/5] vendor: update containerd to acdcf13d5eaf0dfe0eaeabe7194a82535549bc2b Signed-off-by: Tonis Tiigi --- vendor.conf | 4 +- .../containerd/containerd/README.md | 28 + .../containerd/images/archive/exporter.go | 2 +- .../containerd/images/archive/reference.go | 2 +- .../github.com/containerd/containerd/lease.go | 12 +- .../containerd/containerd/oci/spec_opts.go | 16 +- .../containerd/oci/spec_opts_linux.go | 57 ++ .../containerd/oci/spec_opts_unix.go | 57 ++ .../containerd/oci/spec_opts_windows.go | 7 + .../containerd/pkg/process/deleted_state.go | 4 + .../containerd/containerd/pkg/process/exec.go | 14 +- .../containerd/pkg/process/exec_state.go | 13 + .../containerd/containerd/pkg/process/init.go | 28 +- .../containerd/pkg/process/init_state.go | 28 + .../containerd/containerd/pkg/process/io.go | 4 +- .../containerd/pkg/process/utils.go | 15 + .../containerd/reference/docker/reference.go | 797 ++++++++++++++++++ .../containerd/remotes/docker/errcode.go | 283 +++++++ .../containerd/remotes/docker/errdesc.go | 154 ++++ .../containerd/remotes/docker/fetcher.go | 3 +- .../containerd/runtime/v1/linux/task.go | 12 +- .../containerd/runtime/v1/shim/service.go | 6 +- .../runtime/v1/shim/service_linux.go | 4 +- .../containerd/containerd/vendor.conf | 8 +- .../containerd/containerd/version/version.go | 5 + .../runc/libcontainer/apparmor/apparmor.go | 10 +- .../runc/libcontainer/configs/config.go | 1 + .../runc/libcontainer/utils/cmsg.go | 93 ++ .../runc/libcontainer/utils/utils.go | 112 +++ .../runc/libcontainer/utils/utils_unix.go | 68 ++ .../opencontainers/runc/vendor.conf | 2 +- 31 files changed, 1797 insertions(+), 52 deletions(-) create mode 100644 vendor/github.com/containerd/containerd/reference/docker/reference.go create mode 100644 vendor/github.com/containerd/containerd/remotes/docker/errcode.go create mode 100644 vendor/github.com/containerd/containerd/remotes/docker/errdesc.go create mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go create mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go create mode 100644 vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go diff --git a/vendor.conf b/vendor.conf index d55418b12a..d815c92682 100644 --- a/vendor.conf +++ b/vendor.conf @@ -80,7 +80,7 @@ google.golang.org/grpc 6eaf6f47437a6b4e2153a190160e # the containerd project first, and update both after that is merged. # This commit does not need to match RUNC_COMMIT as it is used for helper # packages but should be newer or equal. -github.com/opencontainers/runc 3e425f80a8c931f88e6d94a8c831b9d5aa481657 # v1.0.0-rc8-92-g84373aaa +github.com/opencontainers/runc d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9 github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db github.com/opencontainers/image-spec d60099175f88c47cd379c4738d158884749ed235 # v1.0.1 github.com/seccomp/libseccomp-golang 689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1 @@ -117,7 +117,7 @@ github.com/googleapis/gax-go 317e0006254c44a0ac427cc52a0e google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9 # containerd -github.com/containerd/containerd 36cf5b690dcc00ff0f34ff7799209050c3d0c59a # v1.3.0 +github.com/containerd/containerd acdcf13d5eaf0dfe0eaeabe7194a82535549bc2b github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13 github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c github.com/containerd/cgroups 5fbad35c2a7e855762d3c60f2e474ffcad0d470a diff --git a/vendor/github.com/containerd/containerd/README.md b/vendor/github.com/containerd/containerd/README.md index 2323f26f62..6bab093dfe 100644 --- a/vendor/github.com/containerd/containerd/README.md +++ b/vendor/github.com/containerd/containerd/README.md @@ -210,6 +210,34 @@ See [PLUGINS.md](PLUGINS.md) for how to create plugins Please see [RELEASES.md](RELEASES.md) for details on versioning and stability of containerd components. +Downloadable 64-bit Intel/AMD binaries of all official releases are available on +our [releases page](https://github.com/containerd/containerd/releases), as well as +auto-published to the [cri-containerd-release storage bucket](https://console.cloud.google.com/storage/browser/cri-containerd-release?pli=1). + +For other architectures and distribution support, you will find that many +Linux distributions package their own containerd and provide it across several +architectures, such as [Canonical's Ubuntu packaging](https://launchpad.net/ubuntu/bionic/+package/containerd). + +#### Enabling command auto-completion + +Starting with containerd 1.4, the urfave client feature for auto-creation of bash +autocompletion data is enabled. To use the autocomplete feature in your shell, source +the autocomplete/bash_autocomplete file in your .bashrc file while setting the `PROG` +variable to `ctr`: + +``` +$ PROG=ctr source vendor/github.com/urfave/cli/autocomplete/bash_autocomplete +``` + +#### Distribution of `ctr` autocomplete for bash + +Copy `vendor/github.com/urfave/cli/autocomplete/bash_autocomplete` into +`/etc/bash_completion.d/` and rename it to `ctr`. + +Provide documentation to users to `source` this file into their shell if +you don't place the autocomplete file in a location where it is automatically +loaded for user's bash shell environment. + ### Communication For async communication and long running discussions please use issues and pull requests on the github repo. diff --git a/vendor/github.com/containerd/containerd/images/archive/exporter.go b/vendor/github.com/containerd/containerd/images/archive/exporter.go index 244ef32245..c9d3f6ec7e 100644 --- a/vendor/github.com/containerd/containerd/images/archive/exporter.go +++ b/vendor/github.com/containerd/containerd/images/archive/exporter.go @@ -263,7 +263,7 @@ func getRecords(ctx context.Context, store content.Provider, desc ocispec.Descri images.HandlerFunc(exportHandler), ) - // Walk sequentially since the number of fetchs is likely one and doing in + // Walk sequentially since the number of fetches is likely one and doing in // parallel requires locking the export handler if err := images.Walk(ctx, handlers, desc); err != nil { return nil, err diff --git a/vendor/github.com/containerd/containerd/images/archive/reference.go b/vendor/github.com/containerd/containerd/images/archive/reference.go index cd63517e53..ce9fe98f91 100644 --- a/vendor/github.com/containerd/containerd/images/archive/reference.go +++ b/vendor/github.com/containerd/containerd/images/archive/reference.go @@ -20,7 +20,7 @@ import ( "strings" "github.com/containerd/containerd/reference" - distref "github.com/docker/distribution/reference" + distref "github.com/containerd/containerd/reference/docker" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containerd/containerd/lease.go b/vendor/github.com/containerd/containerd/lease.go index d46b79d9f1..0e7619b8a4 100644 --- a/vendor/github.com/containerd/containerd/lease.go +++ b/vendor/github.com/containerd/containerd/lease.go @@ -24,7 +24,7 @@ import ( ) // WithLease attaches a lease on the context -func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.Context) error, error) { +func (c *Client) WithLease(ctx context.Context, opts ...leases.Opt) (context.Context, func(context.Context) error, error) { _, ok := leases.FromContext(ctx) if ok { return ctx, func(context.Context) error { @@ -34,7 +34,15 @@ func (c *Client) WithLease(ctx context.Context) (context.Context, func(context.C ls := c.LeasesService() - l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour)) + if len(opts) == 0 { + // Use default lease configuration if no options provided + opts = []leases.Opt{ + leases.WithRandomID(), + leases.WithExpiration(24 * time.Hour), + } + } + + l, err := ls.Create(ctx, opts...) if err != nil { return nil, nil, err } diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts.go b/vendor/github.com/containerd/containerd/oci/spec_opts.go index ad6b52a9f5..51818acbce 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts.go @@ -1006,6 +1006,21 @@ func WithParentCgroupDevices(_ context.Context, _ Client, _ *containers.Containe return nil } +// WithAllDevicesAllowed permits READ WRITE MKNOD on all devices nodes for the container +func WithAllDevicesAllowed(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + if s.Linux.Resources == nil { + s.Linux.Resources = &specs.LinuxResources{} + } + s.Linux.Resources.Devices = []specs.LinuxDeviceCgroup{ + { + Allow: true, + Access: rwm, + }, + } + return nil +} + // WithDefaultUnixDevices adds the default devices for unix such as /dev/null, /dev/random to // the container's resource cgroup spec func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { @@ -1100,7 +1115,6 @@ func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container } // WithPrivileged sets up options for a privileged container -// TODO(justincormack) device handling var WithPrivileged = Compose( WithAllCapabilities, WithMaskedPaths(nil), diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_linux.go b/vendor/github.com/containerd/containerd/oci/spec_opts_linux.go index 918c8f4ec3..1448ee78f6 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_linux.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts_linux.go @@ -19,12 +19,69 @@ package oci import ( + "context" + "io/ioutil" "os" + "path/filepath" + "github.com/containerd/containerd/containers" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" ) +// WithHostDevices adds all the hosts device nodes to the container's spec +func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + + devs, err := getDevices("/dev") + if err != nil { + return err + } + s.Linux.Devices = append(s.Linux.Devices, devs...) + return nil +} + +func getDevices(path string) ([]specs.LinuxDevice, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + var out []specs.LinuxDevice + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + // ".udev" added to address https://github.com/opencontainers/runc/issues/2093 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + + out = append(out, sub...) + continue + } + case f.Name() == "console": + continue + } + device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm") + if err != nil { + if err == ErrNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) { var stat unix.Stat_t if err := unix.Lstat(path, &stat); err != nil { diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go b/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go index 3f63dfd162..bcabf0efb5 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts_unix.go @@ -19,12 +19,69 @@ package oci import ( + "context" + "io/ioutil" "os" + "path/filepath" + "github.com/containerd/containerd/containers" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" ) +// WithHostDevices adds all the hosts device nodes to the container's spec +func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + setLinux(s) + + devs, err := getDevices("/dev") + if err != nil { + return err + } + s.Linux.Devices = append(s.Linux.Devices, devs...) + return nil +} + +func getDevices(path string) ([]specs.LinuxDevice, error) { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + var out []specs.LinuxDevice + for _, f := range files { + switch { + case f.IsDir(): + switch f.Name() { + // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 + // ".udev" added to address https://github.com/opencontainers/runc/issues/2093 + case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev": + continue + default: + sub, err := getDevices(filepath.Join(path, f.Name())) + if err != nil { + return nil, err + } + + out = append(out, sub...) + continue + } + case f.Name() == "console": + continue + } + device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm") + if err != nil { + if err == ErrNotADevice { + continue + } + if os.IsNotExist(err) { + continue + } + return nil, err + } + out = append(out, *device) + } + return out, nil +} + func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) { var stat unix.Stat_t if err := unix.Lstat(path, &stat); err != nil { diff --git a/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go b/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go index d265d544de..47caf1925b 100644 --- a/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go +++ b/vendor/github.com/containerd/containerd/oci/spec_opts_windows.go @@ -67,6 +67,13 @@ func WithWindowNetworksAllowUnqualifiedDNSQuery() SpecOpts { } } +// WithHostDevices adds all the hosts device nodes to the container's spec +// +// Not supported on windows +func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error { + return nil +} + func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) { return nil, errors.New("device from path not supported on Windows") } diff --git a/vendor/github.com/containerd/containerd/pkg/process/deleted_state.go b/vendor/github.com/containerd/containerd/pkg/process/deleted_state.go index 95ad138e06..eb7baf737c 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/deleted_state.go +++ b/vendor/github.com/containerd/containerd/pkg/process/deleted_state.go @@ -69,3 +69,7 @@ func (s *deletedState) SetExited(status int) { func (s *deletedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) { return nil, errors.Errorf("cannot exec in a deleted state") } + +func (s *deletedState) Status(ctx context.Context) (string, error) { + return "stopped", nil +} diff --git a/vendor/github.com/containerd/containerd/pkg/process/exec.go b/vendor/github.com/containerd/containerd/pkg/process/exec.go index 4175dcd5a4..e8158434a1 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/exec.go +++ b/vendor/github.com/containerd/containerd/pkg/process/exec.go @@ -261,17 +261,5 @@ func (e *execProcess) Status(ctx context.Context) (string, error) { } e.mu.Lock() defer e.mu.Unlock() - // if we don't have a pid(pid=0) then the exec process has just been created - if e.pid.get() == 0 { - return "created", nil - } - if e.pid.get() == StoppedPID { - return "stopped", nil - } - // if we have a pid and it can be signaled, the process is running - if err := unix.Kill(e.pid.get(), 0); err == nil { - return "running", nil - } - // else if we have a pid but it can nolonger be signaled, it has stopped - return "stopped", nil + return e.execState.Status(ctx) } diff --git a/vendor/github.com/containerd/containerd/pkg/process/exec_state.go b/vendor/github.com/containerd/containerd/pkg/process/exec_state.go index a8b44bb8bc..c97b4001b4 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/exec_state.go +++ b/vendor/github.com/containerd/containerd/pkg/process/exec_state.go @@ -31,6 +31,7 @@ type execState interface { Delete(context.Context) error Kill(context.Context, uint32, bool) error SetExited(int) + Status(context.Context) (string, error) } type execCreatedState struct { @@ -82,6 +83,10 @@ func (s *execCreatedState) SetExited(status int) { } } +func (s *execCreatedState) Status(ctx context.Context) (string, error) { + return "created", nil +} + type execRunningState struct { p *execProcess } @@ -120,6 +125,10 @@ func (s *execRunningState) SetExited(status int) { } } +func (s *execRunningState) Status(ctx context.Context) (string, error) { + return "running", nil +} + type execStoppedState struct { p *execProcess } @@ -157,3 +166,7 @@ func (s *execStoppedState) Kill(ctx context.Context, sig uint32, all bool) error func (s *execStoppedState) SetExited(status int) { // no op } + +func (s *execStoppedState) Status(ctx context.Context) (string, error) { + return "stopped", nil +} diff --git a/vendor/github.com/containerd/containerd/pkg/process/init.go b/vendor/github.com/containerd/containerd/pkg/process/init.go index 7861bdd8b6..539f9c24a0 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/init.go +++ b/vendor/github.com/containerd/containerd/pkg/process/init.go @@ -56,12 +56,14 @@ type Init struct { WorkDir string - id string - Bundle string - console console.Console - Platform stdio.Platform - io *processIO - runtime *runc.Runc + id string + Bundle string + console console.Console + Platform stdio.Platform + io *processIO + runtime *runc.Runc + // pausing preserves the pausing state. + pausing *atomicBool status int exited time.Time pid safePid @@ -97,6 +99,7 @@ func New(id string, runtime *runc.Runc, stdio stdio.Stdio) *Init { p := &Init{ id: id, runtime: runtime, + pausing: new(atomicBool), stdio: stdio, status: 0, waitBlock: make(chan struct{}), @@ -237,17 +240,14 @@ func (p *Init) ExitedAt() time.Time { // Status of the process func (p *Init) Status(ctx context.Context) (string, error) { + if p.pausing.get() { + return "pausing", nil + } + p.mu.Lock() defer p.mu.Unlock() - c, err := p.runtime.State(ctx, p.id) - if err != nil { - if strings.Contains(err.Error(), "does not exist") { - return "stopped", nil - } - return "", p.runtimeError(err, "OCI runtime state failed") - } - return c.Status, nil + return p.initState.Status(ctx) } // Start the init process diff --git a/vendor/github.com/containerd/containerd/pkg/process/init_state.go b/vendor/github.com/containerd/containerd/pkg/process/init_state.go index 9ec1d17be0..115d8c9fb4 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/init_state.go +++ b/vendor/github.com/containerd/containerd/pkg/process/init_state.go @@ -37,6 +37,7 @@ type initState interface { Exec(context.Context, string, *ExecConfig) (Process, error) Kill(context.Context, uint32, bool) error SetExited(int) + Status(context.Context) (string, error) } type createdState struct { @@ -103,6 +104,10 @@ func (s *createdState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr return s.p.exec(ctx, path, r) } +func (s *createdState) Status(ctx context.Context) (string, error) { + return "created", nil +} + type createdCheckpointState struct { p *Init opts *runc.RestoreOpts @@ -211,6 +216,10 @@ func (s *createdCheckpointState) Exec(ctx context.Context, path string, r *ExecC return nil, errors.Errorf("cannot exec in a created state") } +func (s *createdCheckpointState) Status(ctx context.Context) (string, error) { + return "created", nil +} + type runningState struct { p *Init } @@ -228,6 +237,13 @@ func (s *runningState) transition(name string) error { } func (s *runningState) Pause(ctx context.Context) error { + s.p.pausing.set(true) + // NOTE "pausing" will be returned in the short window + // after `transition("paused")`, before `pausing` is reset + // to false. That doesn't break the state machine, just + // delays the "paused" state a little bit. + defer s.p.pausing.set(false) + if err := s.p.runtime.Pause(ctx, s.p.id); err != nil { return s.p.runtimeError(err, "OCI runtime pause failed") } @@ -271,6 +287,10 @@ func (s *runningState) Exec(ctx context.Context, path string, r *ExecConfig) (Pr return s.p.exec(ctx, path, r) } +func (s *runningState) Status(ctx context.Context) (string, error) { + return "running", nil +} + type pausedState struct { p *Init } @@ -335,6 +355,10 @@ func (s *pausedState) Exec(ctx context.Context, path string, r *ExecConfig) (Pro return nil, errors.Errorf("cannot exec in a paused state") } +func (s *pausedState) Status(ctx context.Context) (string, error) { + return "paused", nil +} + type stoppedState struct { p *Init } @@ -387,3 +411,7 @@ func (s *stoppedState) SetExited(status int) { func (s *stoppedState) Exec(ctx context.Context, path string, r *ExecConfig) (Process, error) { return nil, errors.Errorf("cannot exec in a stopped state") } + +func (s *stoppedState) Status(ctx context.Context) (string, error) { + return "stopped", nil +} diff --git a/vendor/github.com/containerd/containerd/pkg/process/io.go b/vendor/github.com/containerd/containerd/pkg/process/io.go index 169f6c8e20..28a94a5ecd 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/io.go +++ b/vendor/github.com/containerd/containerd/pkg/process/io.go @@ -40,7 +40,9 @@ import ( var bufPool = sync.Pool{ New: func() interface{} { - buffer := make([]byte, 32<<10) + // setting to 4096 to align with PIPE_BUF + // http://man7.org/linux/man-pages/man7/pipe.7.html + buffer := make([]byte, 4096) return &buffer }, } diff --git a/vendor/github.com/containerd/containerd/pkg/process/utils.go b/vendor/github.com/containerd/containerd/pkg/process/utils.go index 6de2f7a12c..3433559487 100644 --- a/vendor/github.com/containerd/containerd/pkg/process/utils.go +++ b/vendor/github.com/containerd/containerd/pkg/process/utils.go @@ -27,6 +27,7 @@ import ( "path/filepath" "strings" "sync" + "sync/atomic" "time" "github.com/containerd/containerd/errdefs" @@ -62,6 +63,20 @@ func (s *safePid) set(pid int) { s.Unlock() } +type atomicBool int32 + +func (ab *atomicBool) set(b bool) { + if b { + atomic.StoreInt32((*int32)(ab), 1) + } else { + atomic.StoreInt32((*int32)(ab), 0) + } +} + +func (ab *atomicBool) get() bool { + return atomic.LoadInt32((*int32)(ab)) == 1 +} + // TODO(mlaventure): move to runc package? func getLastRuntimeError(r *runc.Runc) (string, error) { if r.Log == "" { diff --git a/vendor/github.com/containerd/containerd/reference/docker/reference.go b/vendor/github.com/containerd/containerd/reference/docker/reference.go new file mode 100644 index 0000000000..0998639b03 --- /dev/null +++ b/vendor/github.com/containerd/containerd/reference/docker/reference.go @@ -0,0 +1,797 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package docker provides a general type to represent any way of referencing images within the registry. +// Its main purpose is to abstract tags and digests (content-addressable hash). +// +// Grammar +// +// reference := name [ ":" tag ] [ "@" digest ] +// name := [domain '/'] path-component ['/' path-component]* +// domain := domain-component ['.' domain-component]* [':' port-number] +// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// port-number := /[0-9]+/ +// path-component := alpha-numeric [separator alpha-numeric]* +// alpha-numeric := /[a-z0-9]+/ +// separator := /[_.]|__|[-]*/ +// +// tag := /[\w][\w.-]{0,127}/ +// +// digest := digest-algorithm ":" digest-hex +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* +// digest-algorithm-separator := /[+.-_]/ +// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ +// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value +// +// identifier := /[a-f0-9]{64}/ +// short-identifier := /[a-f0-9]{6,64}/ +package docker + +import ( + "errors" + "fmt" + "path" + "regexp" + "strings" + + "github.com/opencontainers/go-digest" +) + +const ( + // NameTotalLengthMax is the maximum total number of characters in a repository name. + NameTotalLengthMax = 255 +) + +var ( + // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. + ErrReferenceInvalidFormat = errors.New("invalid reference format") + + // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. + ErrTagInvalidFormat = errors.New("invalid tag format") + + // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. + ErrDigestInvalidFormat = errors.New("invalid digest format") + + // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. + ErrNameContainsUppercase = errors.New("repository name must be lowercase") + + // ErrNameEmpty is returned for empty, invalid repository names. + ErrNameEmpty = errors.New("repository name must have at least one component") + + // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. + ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) + + // ErrNameNotCanonical is returned when a name is not canonical. + ErrNameNotCanonical = errors.New("repository name must be canonical") +) + +// Reference is an opaque object reference identifier that may include +// modifiers such as a hostname, name, tag, and digest. +type Reference interface { + // String returns the full reference + String() string +} + +// Field provides a wrapper type for resolving correct reference types when +// working with encoding. +type Field struct { + reference Reference +} + +// AsField wraps a reference in a Field for encoding. +func AsField(reference Reference) Field { + return Field{reference} +} + +// Reference unwraps the reference type from the field to +// return the Reference object. This object should be +// of the appropriate type to further check for different +// reference types. +func (f Field) Reference() Reference { + return f.reference +} + +// MarshalText serializes the field to byte text which +// is the string of the reference. +func (f Field) MarshalText() (p []byte, err error) { + return []byte(f.reference.String()), nil +} + +// UnmarshalText parses text bytes by invoking the +// reference parser to ensure the appropriately +// typed reference object is wrapped by field. +func (f *Field) UnmarshalText(p []byte) error { + r, err := Parse(string(p)) + if err != nil { + return err + } + + f.reference = r + return nil +} + +// Named is an object with a full name +type Named interface { + Reference + Name() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string +} + +// NamedTagged is an object including a name and tag. +type NamedTagged interface { + Named + Tag() string +} + +// Digested is an object which has a digest +// in which it can be referenced by +type Digested interface { + Reference + Digest() digest.Digest +} + +// Canonical reference is an object with a fully unique +// name including a name with domain and digest +type Canonical interface { + Named + Digest() digest.Digest +} + +// namedRepository is a reference to a repository with a name. +// A namedRepository has both domain and path components. +type namedRepository interface { + Named + Domain() string + Path() string +} + +// Domain returns the domain part of the Named reference +func Domain(named Named) string { + if r, ok := named.(namedRepository); ok { + return r.Domain() + } + domain, _ := splitDomain(named.Name()) + return domain +} + +// Path returns the name without the domain part of the Named reference +func Path(named Named) (name string) { + if r, ok := named.(namedRepository); ok { + return r.Path() + } + _, path := splitDomain(named.Name()) + return path +} + +func splitDomain(name string) (string, string) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if len(match) != 3 { + return "", name + } + return match[1], match[2] +} + +// SplitHostname splits a named reference into a +// hostname and name string. If no valid hostname is +// found, the hostname is empty and the full value +// is returned as name +// DEPRECATED: Use Domain or Path +func SplitHostname(named Named) (string, string) { + if r, ok := named.(namedRepository); ok { + return r.Domain(), r.Path() + } + return splitDomain(named.Name()) +} + +// Parse parses s and returns a syntactically valid Reference. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: Parse will not handle short digests. +func Parse(s string) (Reference, error) { + matches := ReferenceRegexp.FindStringSubmatch(s) + if matches == nil { + if s == "" { + return nil, ErrNameEmpty + } + if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { + return nil, ErrNameContainsUppercase + } + return nil, ErrReferenceInvalidFormat + } + + if len(matches[1]) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + var repo repository + + nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + if len(nameMatch) == 3 { + repo.domain = nameMatch[1] + repo.path = nameMatch[2] + } else { + repo.domain = "" + repo.path = matches[1] + } + + ref := reference{ + namedRepository: repo, + tag: matches[2], + } + if matches[3] != "" { + var err error + ref.digest, err = digest.Parse(matches[3]) + if err != nil { + return nil, err + } + } + + r := getBestReferenceType(ref) + if r == nil { + return nil, ErrNameEmpty + } + + return r, nil +} + +// ParseNamed parses s and returns a syntactically valid reference implementing +// the Named interface. The reference must have a name and be in the canonical +// form, otherwise an error is returned. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: ParseNamed will not handle short digests. +func ParseNamed(s string) (Named, error) { + named, err := ParseNormalizedNamed(s) + if err != nil { + return nil, err + } + if named.String() != s { + return nil, ErrNameNotCanonical + } + return named, nil +} + +// WithName returns a named object representing the given string. If the input +// is invalid ErrReferenceInvalidFormat will be returned. +func WithName(name string) (Named, error) { + if len(name) > NameTotalLengthMax { + return nil, ErrNameTooLong + } + + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat + } + return repository{ + domain: match[1], + path: match[2], + }, nil +} + +// WithTag combines the name from "name" and the tag from "tag" to form a +// reference incorporating both the name and the tag. +func WithTag(name Named, tag string) (NamedTagged, error) { + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if canonical, ok := name.(Canonical); ok { + return reference{ + namedRepository: repo, + tag: tag, + digest: canonical.Digest(), + }, nil + } + return taggedReference{ + namedRepository: repo, + tag: tag, + }, nil +} + +// WithDigest combines the name from "name" and the digest from "digest" to form +// a reference incorporating both the name and the digest. +func WithDigest(name Named, digest digest.Digest) (Canonical, error) { + if !anchoredDigestRegexp.MatchString(digest.String()) { + return nil, ErrDigestInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if tagged, ok := name.(Tagged); ok { + return reference{ + namedRepository: repo, + tag: tagged.Tag(), + digest: digest, + }, nil + } + return canonicalReference{ + namedRepository: repo, + digest: digest, + }, nil +} + +// TrimNamed removes any tag or digest from the named reference. +func TrimNamed(ref Named) Named { + domain, path := SplitHostname(ref) + return repository{ + domain: domain, + path: path, + } +} + +func getBestReferenceType(ref reference) Reference { + if ref.Name() == "" { + // Allow digest only references + if ref.digest != "" { + return digestReference(ref.digest) + } + return nil + } + if ref.tag == "" { + if ref.digest != "" { + return canonicalReference{ + namedRepository: ref.namedRepository, + digest: ref.digest, + } + } + return ref.namedRepository + } + if ref.digest == "" { + return taggedReference{ + namedRepository: ref.namedRepository, + tag: ref.tag, + } + } + + return ref +} + +type reference struct { + namedRepository + tag string + digest digest.Digest +} + +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() +} + +func (r reference) Tag() string { + return r.tag +} + +func (r reference) Digest() digest.Digest { + return r.digest +} + +type repository struct { + domain string + path string +} + +func (r repository) String() string { + return r.Name() +} + +func (r repository) Name() string { + if r.domain == "" { + return r.path + } + return r.domain + "/" + r.path +} + +func (r repository) Domain() string { + return r.domain +} + +func (r repository) Path() string { + return r.path +} + +type digestReference digest.Digest + +func (d digestReference) String() string { + return digest.Digest(d).String() +} + +func (d digestReference) Digest() digest.Digest { + return digest.Digest(d) +} + +type taggedReference struct { + namedRepository + tag string +} + +func (t taggedReference) String() string { + return t.Name() + ":" + t.tag +} + +func (t taggedReference) Tag() string { + return t.tag +} + +type canonicalReference struct { + namedRepository + digest digest.Digest +} + +func (c canonicalReference) String() string { + return c.Name() + "@" + c.digest.String() +} + +func (c canonicalReference) Digest() digest.Digest { + return c.digest +} + +var ( + // alphaNumericRegexp defines the alpha numeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphaNumericRegexp = match(`[a-z0-9]+`) + + // separatorRegexp defines the separators allowed to be embedded in name + // components. This allow one period, one or two underscore and multiple + // dashes. + separatorRegexp = match(`(?:[._]|__|[-]*)`) + + // nameComponentRegexp restricts registry path component names to start + // with at least one letter or number, with following parts able to be + // separated by one period, one or two underscore and multiple dashes. + nameComponentRegexp = expression( + alphaNumericRegexp, + optional(repeated(separatorRegexp, alphaNumericRegexp))) + + // domainComponentRegexp restricts the registry domain component of a + // repository name to start with a component as defined by DomainRegexp + // and followed by an optional port. + domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) + + // DomainRegexp defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names. + DomainRegexp = expression( + domainComponentRegexp, + optional(repeated(literal(`.`), domainComponentRegexp)), + optional(literal(`:`), match(`[0-9]+`))) + + // TagRegexp matches valid tag names. From docker/docker:graph/tags.go. + TagRegexp = match(`[\w][\w.-]{0,127}`) + + // anchoredTagRegexp matches valid tag names, anchored at the start and + // end of the matched string. + anchoredTagRegexp = anchored(TagRegexp) + + // DigestRegexp matches valid digests. + DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) + + // anchoredDigestRegexp matches valid digests, anchored at the start and + // end of the matched string. + anchoredDigestRegexp = anchored(DigestRegexp) + + // NameRegexp is the format for the name component of references. The + // regexp has capturing groups for the domain and name part omitting + // the separating forward slash from either. + NameRegexp = expression( + optional(DomainRegexp, literal(`/`)), + nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp))) + + // anchoredNameRegexp is used to parse a name value, capturing the + // domain and trailing components. + anchoredNameRegexp = anchored( + optional(capture(DomainRegexp), literal(`/`)), + capture(nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp)))) + + // ReferenceRegexp is the full supported format of a reference. The regexp + // is anchored and has capturing groups for name, tag, and digest + // components. + ReferenceRegexp = anchored(capture(NameRegexp), + optional(literal(":"), capture(TagRegexp)), + optional(literal("@"), capture(DigestRegexp))) + + // IdentifierRegexp is the format for string identifier used as a + // content addressable identifier using sha256. These identifiers + // are like digests without the algorithm, since sha256 is used. + IdentifierRegexp = match(`([a-f0-9]{64})`) + + // ShortIdentifierRegexp is the format used to represent a prefix + // of an identifier. A prefix may be used to match a sha256 identifier + // within a list of trusted identifiers. + ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) + + // anchoredIdentifierRegexp is used to check or match an + // identifier value, anchored at start and end of string. + anchoredIdentifierRegexp = anchored(IdentifierRegexp) +) + +// match compiles the string to a regular expression. +var match = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) *regexp.Regexp { + re := match(regexp.QuoteMeta(s)) + + if _, complete := re.LiteralPrefix(); !complete { + panic("must be a literal") + } + + return re +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...*regexp.Regexp) *regexp.Regexp { + var s string + for _, re := range res { + s += re.String() + } + + return match(s) +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `?`) +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `+`) +} + +// group wraps the regexp in a non-capturing group. +func group(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(?:` + expression(res...).String() + `)`) +} + +// capture wraps the expression in a capturing group. +func capture(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(` + expression(res...).String() + `)`) +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...*regexp.Regexp) *regexp.Regexp { + return match(`^` + expression(res...).String() + `$`) +} + +var ( + legacyDefaultDomain = "index.docker.io" + defaultDomain = "docker.io" + officialRepoName = "library" + defaultTag = "latest" +) + +// normalizedNamed represents a name which has been +// normalized and has a familiar form. A familiar name +// is what is used in Docker UI. An example normalized +// name is "docker.io/library/ubuntu" and corresponding +// familiar name of "ubuntu". +type normalizedNamed interface { + Named + Familiar() Named +} + +// ParseNormalizedNamed parses a string into a named reference +// transforming a familiar name from Docker UI to a fully +// qualified reference. If the value may be an identifier +// use ParseAnyReference. +func ParseNormalizedNamed(s string) (Named, error) { + if ok := anchoredIdentifierRegexp.MatchString(s); ok { + return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) + } + domain, remainder := splitDockerDomain(s) + var remoteName string + if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { + remoteName = remainder[:tagSep] + } else { + remoteName = remainder + } + if strings.ToLower(remoteName) != remoteName { + return nil, errors.New("invalid reference format: repository name must be lowercase") + } + + ref, err := Parse(domain + "/" + remainder) + if err != nil { + return nil, err + } + named, isNamed := ref.(Named) + if !isNamed { + return nil, fmt.Errorf("reference %s has no name", ref.String()) + } + return named, nil +} + +// ParseDockerRef normalizes the image reference following the docker convention. This is added +// mainly for backward compatibility. +// The reference returned can only be either tagged or digested. For reference contains both tag +// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@ +// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as +// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa. +func ParseDockerRef(ref string) (Named, error) { + named, err := ParseNormalizedNamed(ref) + if err != nil { + return nil, err + } + if _, ok := named.(NamedTagged); ok { + if canonical, ok := named.(Canonical); ok { + // The reference is both tagged and digested, only + // return digested. + newNamed, err := WithName(canonical.Name()) + if err != nil { + return nil, err + } + newCanonical, err := WithDigest(newNamed, canonical.Digest()) + if err != nil { + return nil, err + } + return newCanonical, nil + } + } + return TagNameOnly(named), nil +} + +// splitDockerDomain splits a repository name to domain and remotename string. +// If no valid domain is found, the default domain is used. Repository name +// needs to be already validated before. +func splitDockerDomain(name string) (domain, remainder string) { + i := strings.IndexRune(name, '/') + if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { + domain, remainder = defaultDomain, name + } else { + domain, remainder = name[:i], name[i+1:] + } + if domain == legacyDefaultDomain { + domain = defaultDomain + } + if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { + remainder = officialRepoName + "/" + remainder + } + return +} + +// familiarizeName returns a shortened version of the name familiar +// to to the Docker UI. Familiar names have the default domain +// "docker.io" and "library/" repository prefix removed. +// For example, "docker.io/library/redis" will have the familiar +// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". +// Returns a familiarized named only reference. +func familiarizeName(named namedRepository) repository { + repo := repository{ + domain: named.Domain(), + path: named.Path(), + } + + if repo.domain == defaultDomain { + repo.domain = "" + // Handle official repositories which have the pattern "library/" + if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { + repo.path = split[1] + } + } + return repo +} + +func (r reference) Familiar() Named { + return reference{ + namedRepository: familiarizeName(r.namedRepository), + tag: r.tag, + digest: r.digest, + } +} + +func (r repository) Familiar() Named { + return familiarizeName(r) +} + +func (t taggedReference) Familiar() Named { + return taggedReference{ + namedRepository: familiarizeName(t.namedRepository), + tag: t.tag, + } +} + +func (c canonicalReference) Familiar() Named { + return canonicalReference{ + namedRepository: familiarizeName(c.namedRepository), + digest: c.digest, + } +} + +// TagNameOnly adds the default tag "latest" to a reference if it only has +// a repo name. +func TagNameOnly(ref Named) Named { + if IsNameOnly(ref) { + namedTagged, err := WithTag(ref, defaultTag) + if err != nil { + // Default tag must be valid, to create a NamedTagged + // type with non-validated input the WithTag function + // should be used instead + panic(err) + } + return namedTagged + } + return ref +} + +// ParseAnyReference parses a reference string as a possible identifier, +// full digest, or familiar name. +func ParseAnyReference(ref string) (Reference, error) { + if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + return digestReference("sha256:" + ref), nil + } + if dgst, err := digest.Parse(ref); err == nil { + return digestReference(dgst), nil + } + + return ParseNormalizedNamed(ref) +} + +// IsNameOnly returns true if reference only contains a repo name. +func IsNameOnly(ref Named) bool { + if _, ok := ref.(NamedTagged); ok { + return false + } + if _, ok := ref.(Canonical); ok { + return false + } + return true +} + +// FamiliarName returns the familiar name string +// for the given named, familiarizing if needed. +func FamiliarName(ref Named) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().Name() + } + return ref.Name() +} + +// FamiliarString returns the familiar string representation +// for the given reference, familiarizing if needed. +func FamiliarString(ref Reference) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().String() + } + return ref.String() +} + +// FamiliarMatch reports whether ref matches the specified pattern. +// See https://godoc.org/path#Match for supported patterns. +func FamiliarMatch(pattern string, ref Reference) (bool, error) { + matched, err := path.Match(pattern, FamiliarString(ref)) + if namedRef, isNamed := ref.(Named); isNamed && !matched { + matched, _ = path.Match(pattern, FamiliarName(namedRef)) + } + return matched, err +} diff --git a/vendor/github.com/containerd/containerd/remotes/docker/errcode.go b/vendor/github.com/containerd/containerd/remotes/docker/errcode.go new file mode 100644 index 0000000000..7d1e54f2a1 --- /dev/null +++ b/vendor/github.com/containerd/containerd/remotes/docker/errcode.go @@ -0,0 +1,283 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package docker + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ErrorCoder is the base interface for ErrorCode and Error allowing +// users of each to just call ErrorCode to get the real ID of each +type ErrorCoder interface { + ErrorCode() ErrorCode +} + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +var _ error = ErrorCode(0) + +// ErrorCode just returns itself +func (ec ErrorCode) ErrorCode() ErrorCode { + return ec +} + +// Error returns the ID/Value +func (ec ErrorCode) Error() string { + // NOTE(stevvooe): Cannot use message here since it may have unpopulated args. + return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1)) +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// WithMessage creates a new Error struct based on the passed-in info and +// overrides the Message property. +func (ec ErrorCode) WithMessage(message string) Error { + return Error{ + Code: ec, + Message: message, + } +} + +// WithDetail creates a new Error struct based on the passed-in info and +// set the Detail property appropriately +func (ec ErrorCode) WithDetail(detail interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithDetail(detail) +} + +// WithArgs creates a new Error struct and sets the Args slice +func (ec ErrorCode) WithArgs(args ...interface{}) Error { + return Error{ + Code: ec, + Message: ec.Message(), + }.WithArgs(args...) +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + + // TODO(duglin): See if we need an "args" property so we can do the + // variable substitution right before showing the message to the user +} + +var _ error = Error{} + +// ErrorCode returns the ID/Value of this Error +func (e Error) ErrorCode() ErrorCode { + return e.Code +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message) +} + +// WithDetail will return a new Error, based on the current one, but with +// some Detail info added +func (e Error) WithDetail(detail interface{}) Error { + return Error{ + Code: e.Code, + Message: e.Message, + Detail: detail, + } +} + +// WithArgs uses the passed-in list of interface{} as the substitution +// variables in the Error's Message string, but returns a new Error +func (e Error) WithArgs(args ...interface{}) Error { + return Error{ + Code: e.Code, + Message: fmt.Sprintf(e.Code.Message(), args...), + Detail: e.Detail, + } +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCode provides the http status code that is associated with + // this error condition. + HTTPStatusCode int +} + +// ParseErrorCode returns the value by the string error code. +// `ErrorCodeUnknown` will be returned if the error is not known. +func ParseErrorCode(value string) ErrorCode { + ed, ok := idToDescriptors[value] + if ok { + return ed.Code + } + + return ErrorCodeUnknown +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors []error + +var _ error = Errors{} + +func (errs Errors) Error() string { + switch len(errs) { + case 0: + return "" + case 1: + return errs[0].Error() + default: + msg := "errors:\n" + for _, err := range errs { + msg += err.Error() + "\n" + } + return msg + } +} + +// Len returns the current number of errors. +func (errs Errors) Len() int { + return len(errs) +} + +// MarshalJSON converts slice of error, ErrorCode or Error into a +// slice of Error - then serializes +func (errs Errors) MarshalJSON() ([]byte, error) { + var tmpErrs struct { + Errors []Error `json:"errors,omitempty"` + } + + for _, daErr := range errs { + var err Error + + switch daErr := daErr.(type) { + case ErrorCode: + err = daErr.WithDetail(nil) + case Error: + err = daErr + default: + err = ErrorCodeUnknown.WithDetail(daErr) + + } + + // If the Error struct was setup and they forgot to set the + // Message field (meaning its "") then grab it from the ErrCode + msg := err.Message + if msg == "" { + msg = err.Code.Message() + } + + tmpErrs.Errors = append(tmpErrs.Errors, Error{ + Code: err.Code, + Message: msg, + Detail: err.Detail, + }) + } + + return json.Marshal(tmpErrs) +} + +// UnmarshalJSON deserializes []Error and then converts it into slice of +// Error or ErrorCode +func (errs *Errors) UnmarshalJSON(data []byte) error { + var tmpErrs struct { + Errors []Error + } + + if err := json.Unmarshal(data, &tmpErrs); err != nil { + return err + } + + var newErrs Errors + for _, daErr := range tmpErrs.Errors { + // If Message is empty or exactly matches the Code's message string + // then just use the Code, no need for a full Error struct + if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) { + // Error's w/o details get converted to ErrorCode + newErrs = append(newErrs, daErr.Code) + } else { + // Error's w/ details are untouched + newErrs = append(newErrs, Error{ + Code: daErr.Code, + Message: daErr.Message, + Detail: daErr.Detail, + }) + } + } + + *errs = newErrs + return nil +} diff --git a/vendor/github.com/containerd/containerd/remotes/docker/errdesc.go b/vendor/github.com/containerd/containerd/remotes/docker/errdesc.go new file mode 100644 index 0000000000..b2bd4d82bd --- /dev/null +++ b/vendor/github.com/containerd/containerd/remotes/docker/errdesc.go @@ -0,0 +1,154 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package docker + +import ( + "fmt" + "net/http" + "sort" + "sync" +) + +var ( + errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{} + idToDescriptors = map[string]ErrorDescriptor{} + groupToDescriptors = map[string][]ErrorDescriptor{} +) + +var ( + // ErrorCodeUnknown is a generic error that can be used as a last + // resort if there is no situation-specific error message that can be used + ErrorCodeUnknown = Register("errcode", ErrorDescriptor{ + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + HTTPStatusCode: http.StatusInternalServerError, + }) + + // ErrorCodeUnsupported is returned when an operation is not supported. + ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{ + Value: "UNSUPPORTED", + Message: "The operation is unsupported.", + Description: `The operation was unsupported due to a missing + implementation or invalid set of parameters.`, + HTTPStatusCode: http.StatusMethodNotAllowed, + }) + + // ErrorCodeUnauthorized is returned if a request requires + // authentication. + ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{ + Value: "UNAUTHORIZED", + Message: "authentication required", + Description: `The access controller was unable to authenticate + the client. Often this will be accompanied by a + Www-Authenticate HTTP response header indicating how to + authenticate.`, + HTTPStatusCode: http.StatusUnauthorized, + }) + + // ErrorCodeDenied is returned if a client does not have sufficient + // permission to perform an action. + ErrorCodeDenied = Register("errcode", ErrorDescriptor{ + Value: "DENIED", + Message: "requested access to the resource is denied", + Description: `The access controller denied access for the + operation on a resource.`, + HTTPStatusCode: http.StatusForbidden, + }) + + // ErrorCodeUnavailable provides a common error to report unavailability + // of a service or endpoint. + ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{ + Value: "UNAVAILABLE", + Message: "service unavailable", + Description: "Returned when a service is not available", + HTTPStatusCode: http.StatusServiceUnavailable, + }) + + // ErrorCodeTooManyRequests is returned if a client attempts too many + // times to contact a service endpoint. + ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{ + Value: "TOOMANYREQUESTS", + Message: "too many requests", + Description: `Returned when a client attempts to contact a + service too many times`, + HTTPStatusCode: http.StatusTooManyRequests, + }) +) + +var nextCode = 1000 +var registerLock sync.Mutex + +// Register will make the passed-in error known to the environment and +// return a new ErrorCode +func Register(group string, descriptor ErrorDescriptor) ErrorCode { + registerLock.Lock() + defer registerLock.Unlock() + + descriptor.Code = ErrorCode(nextCode) + + if _, ok := idToDescriptors[descriptor.Value]; ok { + panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value)) + } + if _, ok := errorCodeToDescriptors[descriptor.Code]; ok { + panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code)) + } + + groupToDescriptors[group] = append(groupToDescriptors[group], descriptor) + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + + nextCode++ + return descriptor.Code +} + +type byValue []ErrorDescriptor + +func (a byValue) Len() int { return len(a) } +func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value } + +// GetGroupNames returns the list of Error group names that are registered +func GetGroupNames() []string { + keys := []string{} + + for k := range groupToDescriptors { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// GetErrorCodeGroup returns the named group of error descriptors +func GetErrorCodeGroup(name string) []ErrorDescriptor { + desc := groupToDescriptors[name] + sort.Sort(byValue(desc)) + return desc +} + +// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are +// registered, irrespective of what group they're in +func GetErrorAllDescriptors() []ErrorDescriptor { + result := []ErrorDescriptor{} + + for _, group := range GetGroupNames() { + result = append(result, GetErrorCodeGroup(group)...) + } + sort.Sort(byValue(result)) + return result +} diff --git a/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go b/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go index ad8482fa39..2bd295ea6d 100644 --- a/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go +++ b/vendor/github.com/containerd/containerd/remotes/docker/fetcher.go @@ -29,7 +29,6 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" - "github.com/docker/distribution/registry/api/errcode" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -160,7 +159,7 @@ func (r dockerFetcher) open(ctx context.Context, req *request, mediatype string, if resp.StatusCode == http.StatusNotFound { return nil, errors.Wrapf(errdefs.ErrNotFound, "content at %v not found", req.String()) } - var registryErr errcode.Errors + var registryErr Errors if err := json.NewDecoder(resp.Body).Decode(®istryErr); err != nil || registryErr.Len() < 1 { return nil, errors.Errorf("unexpected status code %v: %v", req.String(), resp.Status) } diff --git a/vendor/github.com/containerd/containerd/runtime/v1/linux/task.go b/vendor/github.com/containerd/containerd/runtime/v1/linux/task.go index 0970c3ea3b..6e7f592b82 100644 --- a/vendor/github.com/containerd/containerd/runtime/v1/linux/task.go +++ b/vendor/github.com/containerd/containerd/runtime/v1/linux/task.go @@ -91,9 +91,12 @@ func (t *Task) PID() uint32 { // Delete the task and return the exit status func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) { - rsp, err := t.shim.Delete(ctx, empty) - if err != nil && !errdefs.IsNotFound(err) { - return nil, errdefs.FromGRPC(err) + rsp, shimErr := t.shim.Delete(ctx, empty) + if shimErr != nil { + shimErr = errdefs.FromGRPC(shimErr) + if !errdefs.IsNotFound(shimErr) { + return nil, shimErr + } } t.tasks.Delete(ctx, t.id) if err := t.shim.KillShim(ctx); err != nil { @@ -102,6 +105,9 @@ func (t *Task) Delete(ctx context.Context) (*runtime.Exit, error) { if err := t.bundle.Delete(); err != nil { log.G(ctx).WithError(err).Error("failed to delete bundle") } + if shimErr != nil { + return nil, shimErr + } t.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{ ContainerID: t.id, ExitStatus: rsp.ExitStatus, diff --git a/vendor/github.com/containerd/containerd/runtime/v1/shim/service.go b/vendor/github.com/containerd/containerd/runtime/v1/shim/service.go index a722ea1c23..f3e1f4b7c6 100644 --- a/vendor/github.com/containerd/containerd/runtime/v1/shim/service.go +++ b/vendor/github.com/containerd/containerd/runtime/v1/shim/service.go @@ -55,7 +55,7 @@ var ( empty = &ptypes.Empty{} bufPool = sync.Pool{ New: func() interface{} { - buffer := make([]byte, 32<<10) + buffer := make([]byte, 4096) return &buffer }, } @@ -217,7 +217,7 @@ func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteR return nil, err } if err := p.Delete(ctx); err != nil { - return nil, err + return nil, errdefs.ToGRPC(err) } s.mu.Lock() delete(s.processes, s.id) @@ -240,7 +240,7 @@ func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessReq return nil, err } if err := p.Delete(ctx); err != nil { - return nil, err + return nil, errdefs.ToGRPC(err) } s.mu.Lock() delete(s.processes, r.ID) diff --git a/vendor/github.com/containerd/containerd/runtime/v1/shim/service_linux.go b/vendor/github.com/containerd/containerd/runtime/v1/shim/service_linux.go index a4a4b90a35..65a8666e44 100644 --- a/vendor/github.com/containerd/containerd/runtime/v1/shim/service_linux.go +++ b/vendor/github.com/containerd/containerd/runtime/v1/shim/service_linux.go @@ -55,6 +55,7 @@ func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console io.CopyBuffer(epollConsole, in, *bp) // we need to shutdown epollConsole when pipe broken epollConsole.Shutdown(p.epoller.CloseConsole) + epollConsole.Close() }() } @@ -73,9 +74,8 @@ func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console p := bufPool.Get().(*[]byte) defer bufPool.Put(p) io.CopyBuffer(outw, epollConsole, *p) - epollConsole.Close() - outr.Close() outw.Close() + outr.Close() wg.Done() }() cwg.Wait() diff --git a/vendor/github.com/containerd/containerd/vendor.conf b/vendor/github.com/containerd/containerd/vendor.conf index 24724fffe6..b3a55c4255 100644 --- a/vendor/github.com/containerd/containerd/vendor.conf +++ b/vendor/github.com/containerd/containerd/vendor.conf @@ -1,6 +1,6 @@ github.com/containerd/go-runc e029b79d8cda8374981c64eba71f28ec38e5526f github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f -github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9 +github.com/containerd/cgroups abd0b19954a6b05e0963f48427062d1481b7faad github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13 github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877 @@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.2.1 github.com/gogo/googleapis v1.2.0 github.com/golang/protobuf v1.2.0 github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db -github.com/opencontainers/runc 3e425f80a8c931f88e6d94a8c831b9d5aa481657 # v1.0.0-rc8+ CVE-2019-16884 +github.com/opencontainers/runc d736ef14f0288d6993a1845745d6756cfc9ddd5a # v1.0.0-rc9 github.com/konsorten/go-windows-terminal-sequences v1.0.1 github.com/sirupsen/logrus v1.4.1 github.com/urfave/cli v1.22.0 @@ -51,8 +51,8 @@ github.com/cpuguy83/go-md2man v1.0.10 github.com/russross/blackfriday v1.5.2 # cri dependencies -github.com/containerd/cri 5d49e7e51b43e36a6b9c4386257c7d08c602237f # release/1.3 -github.com/containerd/go-cni 49fbd9b210f3c8ee3b7fd3cd797aabaf364627c1 +github.com/containerd/cri 0ebf032aac5f6029f95a94e42161e9db7a7e84df # release/1.3+ +github.com/containerd/go-cni 0d360c50b10b350b6bb23863fd4dfb1c232b01c9 github.com/containernetworking/cni v0.7.1 github.com/containernetworking/plugins v0.7.6 github.com/davecgh/go-spew v1.1.1 diff --git a/vendor/github.com/containerd/containerd/version/version.go b/vendor/github.com/containerd/containerd/version/version.go index 04cf59c87e..aa250aa5e0 100644 --- a/vendor/github.com/containerd/containerd/version/version.go +++ b/vendor/github.com/containerd/containerd/version/version.go @@ -16,6 +16,8 @@ package version +import "runtime" + var ( // Package is filled at linking time Package = "github.com/containerd/containerd" @@ -26,4 +28,7 @@ var ( // Revision is filled with the VCS (e.g. git) revision being used to build // the program at linking time. Revision = "" + + // GoVersion is Go tree's version. + GoVersion = runtime.Version() ) diff --git a/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go b/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go index 7fff0627fa..debfc1e489 100644 --- a/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go +++ b/vendor/github.com/opencontainers/runc/libcontainer/apparmor/apparmor.go @@ -6,6 +6,8 @@ import ( "fmt" "io/ioutil" "os" + + "github.com/opencontainers/runc/libcontainer/utils" ) // IsEnabled returns true if apparmor is enabled for the host. @@ -19,7 +21,7 @@ func IsEnabled() bool { return false } -func setprocattr(attr, value string) error { +func setProcAttr(attr, value string) error { // Under AppArmor you can only change your own attr, so use /proc/self/ // instead of /proc// like libapparmor does path := fmt.Sprintf("/proc/self/attr/%s", attr) @@ -30,6 +32,10 @@ func setprocattr(attr, value string) error { } defer f.Close() + if err := utils.EnsureProcHandle(f); err != nil { + return err + } + _, err = fmt.Fprintf(f, "%s", value) return err } @@ -37,7 +43,7 @@ func setprocattr(attr, value string) error { // changeOnExec reimplements aa_change_onexec from libapparmor in Go func changeOnExec(name string) error { value := "exec " + name - if err := setprocattr("exec", value); err != nil { + if err := setProcAttr("exec", value); err != nil { return fmt.Errorf("apparmor failed to apply profile: %s", err) } return nil diff --git a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go index 7728522fef..24989e9f53 100644 --- a/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go +++ b/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go @@ -44,6 +44,7 @@ const ( Trap Allow Trace + Log ) // Operator is a comparison operator to be used when matching syscall arguments in Seccomp diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go new file mode 100644 index 0000000000..c8a9364d54 --- /dev/null +++ b/vendor/github.com/opencontainers/runc/libcontainer/utils/cmsg.go @@ -0,0 +1,93 @@ +// +build linux + +package utils + +/* + * Copyright 2016, 2017 SUSE LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ( + "fmt" + "os" + + "golang.org/x/sys/unix" +) + +// MaxSendfdLen is the maximum length of the name of a file descriptor being +// sent using SendFd. The name of the file handle returned by RecvFd will never +// be larger than this value. +const MaxNameLen = 4096 + +// oobSpace is the size of the oob slice required to store a single FD. Note +// that unix.UnixRights appears to make the assumption that fd is always int32, +// so sizeof(fd) = 4. +var oobSpace = unix.CmsgSpace(4) + +// RecvFd waits for a file descriptor to be sent over the given AF_UNIX +// socket. The file name of the remote file descriptor will be recreated +// locally (it is sent as non-auxiliary data in the same payload). +func RecvFd(socket *os.File) (*os.File, error) { + // For some reason, unix.Recvmsg uses the length rather than the capacity + // when passing the msg_controllen and other attributes to recvmsg. So we + // have to actually set the length. + name := make([]byte, MaxNameLen) + oob := make([]byte, oobSpace) + + sockfd := socket.Fd() + n, oobn, _, _, err := unix.Recvmsg(int(sockfd), name, oob, 0) + if err != nil { + return nil, err + } + + if n >= MaxNameLen || oobn != oobSpace { + return nil, fmt.Errorf("recvfd: incorrect number of bytes read (n=%d oobn=%d)", n, oobn) + } + + // Truncate. + name = name[:n] + oob = oob[:oobn] + + scms, err := unix.ParseSocketControlMessage(oob) + if err != nil { + return nil, err + } + if len(scms) != 1 { + return nil, fmt.Errorf("recvfd: number of SCMs is not 1: %d", len(scms)) + } + scm := scms[0] + + fds, err := unix.ParseUnixRights(&scm) + if err != nil { + return nil, err + } + if len(fds) != 1 { + return nil, fmt.Errorf("recvfd: number of fds is not 1: %d", len(fds)) + } + fd := uintptr(fds[0]) + + return os.NewFile(fd, string(name)), nil +} + +// SendFd sends a file descriptor over the given AF_UNIX socket. In +// addition, the file.Name() of the given file will also be sent as +// non-auxiliary data in the same payload (allowing to send contextual +// information for a file descriptor). +func SendFd(socket *os.File, name string, fd uintptr) error { + if len(name) >= MaxNameLen { + return fmt.Errorf("sendfd: filename too long: %s", name) + } + oob := unix.UnixRights(int(fd)) + return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0) +} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go new file mode 100644 index 0000000000..40ccfaa1a0 --- /dev/null +++ b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils.go @@ -0,0 +1,112 @@ +package utils + +import ( + "encoding/json" + "io" + "os" + "path/filepath" + "strings" + "unsafe" + + "golang.org/x/sys/unix" +) + +const ( + exitSignalOffset = 128 +) + +// ResolveRootfs ensures that the current working directory is +// not a symlink and returns the absolute path to the rootfs +func ResolveRootfs(uncleanRootfs string) (string, error) { + rootfs, err := filepath.Abs(uncleanRootfs) + if err != nil { + return "", err + } + return filepath.EvalSymlinks(rootfs) +} + +// ExitStatus returns the correct exit status for a process based on if it +// was signaled or exited cleanly +func ExitStatus(status unix.WaitStatus) int { + if status.Signaled() { + return exitSignalOffset + int(status.Signal()) + } + return status.ExitStatus() +} + +// WriteJSON writes the provided struct v to w using standard json marshaling +func WriteJSON(w io.Writer, v interface{}) error { + data, err := json.Marshal(v) + if err != nil { + return err + } + _, err = w.Write(data) + return err +} + +// CleanPath makes a path safe for use with filepath.Join. This is done by not +// only cleaning the path, but also (if the path is relative) adding a leading +// '/' and cleaning it (then removing the leading '/'). This ensures that a +// path resulting from prepending another path will always resolve to lexically +// be a subdirectory of the prefixed path. This is all done lexically, so paths +// that include symlinks won't be safe as a result of using CleanPath. +func CleanPath(path string) string { + // Deal with empty strings nicely. + if path == "" { + return "" + } + + // Ensure that all paths are cleaned (especially problematic ones like + // "/../../../../../" which can cause lots of issues). + path = filepath.Clean(path) + + // If the path isn't absolute, we need to do more processing to fix paths + // such as "../../../..//some/path". We also shouldn't convert absolute + // paths to relative ones. + if !filepath.IsAbs(path) { + path = filepath.Clean(string(os.PathSeparator) + path) + // This can't fail, as (by definition) all paths are relative to root. + path, _ = filepath.Rel(string(os.PathSeparator), path) + } + + // Clean the path again for good measure. + return filepath.Clean(path) +} + +// SearchLabels searches a list of key-value pairs for the provided key and +// returns the corresponding value. The pairs must be separated with '='. +func SearchLabels(labels []string, query string) string { + for _, l := range labels { + parts := strings.SplitN(l, "=", 2) + if len(parts) < 2 { + continue + } + if parts[0] == query { + return parts[1] + } + } + return "" +} + +// Annotations returns the bundle path and user defined annotations from the +// libcontainer state. We need to remove the bundle because that is a label +// added by libcontainer. +func Annotations(labels []string) (bundle string, userAnnotations map[string]string) { + userAnnotations = make(map[string]string) + for _, l := range labels { + parts := strings.SplitN(l, "=", 2) + if len(parts) < 2 { + continue + } + if parts[0] == "bundle" { + bundle = parts[1] + } else { + userAnnotations[parts[0]] = parts[1] + } + } + return +} + +func GetIntSize() int { + return int(unsafe.Sizeof(1)) +} diff --git a/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go new file mode 100644 index 0000000000..1576f2d4ab --- /dev/null +++ b/vendor/github.com/opencontainers/runc/libcontainer/utils/utils_unix.go @@ -0,0 +1,68 @@ +// +build !windows + +package utils + +import ( + "fmt" + "os" + "strconv" + + "golang.org/x/sys/unix" +) + +// EnsureProcHandle returns whether or not the given file handle is on procfs. +func EnsureProcHandle(fh *os.File) error { + var buf unix.Statfs_t + if err := unix.Fstatfs(int(fh.Fd()), &buf); err != nil { + return fmt.Errorf("ensure %s is on procfs: %v", fh.Name(), err) + } + if buf.Type != unix.PROC_SUPER_MAGIC { + return fmt.Errorf("%s is not on procfs", fh.Name()) + } + return nil +} + +// CloseExecFrom applies O_CLOEXEC to all file descriptors currently open for +// the process (except for those below the given fd value). +func CloseExecFrom(minFd int) error { + fdDir, err := os.Open("/proc/self/fd") + if err != nil { + return err + } + defer fdDir.Close() + + if err := EnsureProcHandle(fdDir); err != nil { + return err + } + + fdList, err := fdDir.Readdirnames(-1) + if err != nil { + return err + } + for _, fdStr := range fdList { + fd, err := strconv.Atoi(fdStr) + // Ignore non-numeric file names. + if err != nil { + continue + } + // Ignore descriptors lower than our specified minimum. + if fd < minFd { + continue + } + // Intentionally ignore errors from unix.CloseOnExec -- the cases where + // this might fail are basically file descriptors that have already + // been closed (including and especially the one that was created when + // ioutil.ReadDir did the "opendir" syscall). + unix.CloseOnExec(fd) + } + return nil +} + +// NewSockPair returns a new unix socket pair +func NewSockPair(name string) (parent *os.File, child *os.File, err error) { + fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil +} diff --git a/vendor/github.com/opencontainers/runc/vendor.conf b/vendor/github.com/opencontainers/runc/vendor.conf index e3f8e6d7ea..a29764cd73 100644 --- a/vendor/github.com/opencontainers/runc/vendor.conf +++ b/vendor/github.com/opencontainers/runc/vendor.conf @@ -6,7 +6,7 @@ github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # Core libcontainer functionality. github.com/checkpoint-restore/go-criu 17b0214f6c48980c45dc47ecb0cfd6d9e02df723 # v3.11 github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 -github.com/opencontainers/selinux 3a1f366feb7aecbf7a0e71ac4cea88b31597de9e # v1.2.2 +github.com/opencontainers/selinux 5215b1806f52b1fcc2070a8826c542c9d33cd3cf # v1.3.0 (+ CVE-2019-16884) github.com/seccomp/libseccomp-golang 689e3c1541a84461afc49c1c87352a6cedf72e9c # v0.9.1 github.com/sirupsen/logrus 8bdbc7bcc01dcbb8ec23dc8a28e332258d25251f # v1.4.1 github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2 From 21dfcc730b4c06cd7e070f93e0bda250427dc9fb Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 21 Oct 2019 23:15:56 -0700 Subject: [PATCH 5/5] builder-next: clear temp leases on startup Signed-off-by: Tonis Tiigi --- builder/builder-next/adapters/snapshot/snapshot.go | 2 -- builder/builder-next/controller.go | 9 +++++++++ builder/builder-next/imagerefchecker/checker.go | 1 - 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/builder/builder-next/adapters/snapshot/snapshot.go b/builder/builder-next/adapters/snapshot/snapshot.go index ff05b5632d..f8737d3995 100644 --- a/builder/builder-next/adapters/snapshot/snapshot.go +++ b/builder/builder-next/adapters/snapshot/snapshot.go @@ -75,8 +75,6 @@ func NewSnapshotter(opt Opt, prevLM leases.Manager) (snapshot.Snapshotter, lease lm := newLeaseManager(s, prevLM) - // TODO: temp-leases - ll, err := lm.List(context.TODO()) if err != nil { return nil, nil, err diff --git a/builder/builder-next/controller.go b/builder/builder-next/controller.go index e42c128e92..d7d4b5b9f4 100644 --- a/builder/builder-next/controller.go +++ b/builder/builder-next/controller.go @@ -1,6 +1,7 @@ package buildkit import ( + "context" "net/http" "os" "path/filepath" @@ -166,6 +167,14 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) { return nil, err } + leases, err := lm.List(context.TODO(), "labels.\"buildkit/lease.temporary\"") + if err != nil { + return nil, err + } + for _, l := range leases { + lm.Delete(context.TODO(), l) + } + wopt := mobyworker.Opt{ ID: "moby", MetadataStore: md, diff --git a/builder/builder-next/imagerefchecker/checker.go b/builder/builder-next/imagerefchecker/checker.go index dde61f4c83..01dfab37d5 100644 --- a/builder/builder-next/imagerefchecker/checker.go +++ b/builder/builder-next/imagerefchecker/checker.go @@ -59,7 +59,6 @@ type checker struct { } func (c *checker) Exists(key string, chain []digest.Digest) bool { - // TODO: neeeds update if c.opt.ImageStore == nil { return false }