mirror of
https://github.com/containers/skopeo.git
synced 2026-01-30 13:58:48 +00:00
Compare commits
118 Commits
v0.1.27
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5c9d5790c | ||
|
|
25629ee972 | ||
|
|
77d5469d2b | ||
|
|
29835bb7ee | ||
|
|
fd338a6a8c | ||
|
|
1715c90841 | ||
|
|
187299a20b | ||
|
|
89d8bddd9b | ||
|
|
ba649c56bf | ||
|
|
3456577268 | ||
|
|
b52e700666 | ||
|
|
ee32f1f7aa | ||
|
|
5af0da9de6 | ||
|
|
879a6d793f | ||
|
|
2734f93e30 | ||
|
|
2b97124e4a | ||
|
|
7815a5801e | ||
|
|
501e1be3cf | ||
|
|
fc386a6dca | ||
|
|
2a134a0ddd | ||
|
|
17250d7e8d | ||
|
|
65d28709c3 | ||
|
|
d6c6c78d1b | ||
|
|
67ffa00b1d | ||
|
|
a581847345 | ||
|
|
bcd26a4ae4 | ||
|
|
e38c345f23 | ||
|
|
0421fb04c2 | ||
|
|
82186b916f | ||
|
|
15eed5beda | ||
|
|
81837bd55b | ||
|
|
3dec6a1cdf | ||
|
|
fe14427129 | ||
|
|
be27588418 | ||
|
|
fb84437cd1 | ||
|
|
d9b495ca38 | ||
|
|
6b93d4794f | ||
|
|
5d3849a510 | ||
|
|
fef142f811 | ||
|
|
2684e51aa5 | ||
|
|
e814f9605a | ||
|
|
5d136a46ed | ||
|
|
b0b750dfa1 | ||
|
|
e3034e1d91 | ||
|
|
1a259b76da | ||
|
|
ae64ff7084 | ||
|
|
d67d3a4620 | ||
|
|
196bc48723 | ||
|
|
1c6c7bc481 | ||
|
|
6e23a32282 | ||
|
|
f398c9c035 | ||
|
|
0144aa8dc5 | ||
|
|
0df5dcf09c | ||
|
|
f9baaa6b87 | ||
|
|
67ff78925b | ||
|
|
5c611083f2 | ||
|
|
976d57ea45 | ||
|
|
63569fcd63 | ||
|
|
98b3a13b46 | ||
|
|
ca3bff6a7c | ||
|
|
563a4ac523 | ||
|
|
14ea9f8bfd | ||
|
|
05e38e127e | ||
|
|
1ef80d8082 | ||
|
|
597b6bd204 | ||
|
|
7e9a664764 | ||
|
|
79449a358d | ||
|
|
2d04db9ac8 | ||
|
|
3e7a28481c | ||
|
|
79225f2e65 | ||
|
|
e1c1bbf26d | ||
|
|
c4808f002e | ||
|
|
42203b366d | ||
|
|
1f11b8b350 | ||
|
|
ea23621c70 | ||
|
|
ab2bc6e8d1 | ||
|
|
c520041b83 | ||
|
|
e626fca6a7 | ||
|
|
92b6262224 | ||
|
|
e8dea9e770 | ||
|
|
28080c8d5f | ||
|
|
0cea6dde02 | ||
|
|
22482e099a | ||
|
|
7aba888e99 | ||
|
|
c61482d2cf | ||
|
|
db941ebd8f | ||
|
|
7add6fc80b | ||
|
|
eb9d74090e | ||
|
|
61351d44d7 | ||
|
|
aa73bd9d0d | ||
|
|
b08350db15 | ||
|
|
f63f78225d | ||
|
|
60aa4aa82d | ||
|
|
37264e21fb | ||
|
|
fe2591054c | ||
|
|
fd0c3d7f08 | ||
|
|
b325cc22b8 | ||
|
|
5f754820da | ||
|
|
43acc747d5 | ||
|
|
b3dec98757 | ||
|
|
b1795a08fb | ||
|
|
1307cac0c2 | ||
|
|
dc1567c8bc | ||
|
|
22c524b0e0 | ||
|
|
9a225c3968 | ||
|
|
0270e5694c | ||
|
|
4ff902dab9 | ||
|
|
64b3bd28e3 | ||
|
|
d8e506c648 | ||
|
|
aa6c809e5a | ||
|
|
1c27d6918f | ||
|
|
9f2491694d | ||
|
|
14245f2e24 | ||
|
|
8a1d480274 | ||
|
|
78b29a5c2f | ||
|
|
20d31daec0 | ||
|
|
5a8f212630 | ||
|
|
34e77f9897 |
@@ -15,7 +15,7 @@ that we follow.
|
||||
## Reporting Issues
|
||||
|
||||
Before reporting an issue, check our backlog of
|
||||
[open issues](https://github.com/projectatomic/skopeo/issues)
|
||||
[open issues](https://github.com/containers/skopeo/issues)
|
||||
to see if someone else has already reported it. If so, feel free to add
|
||||
your scenario, or additional information, to the discussion. Or simply
|
||||
"subscribe" to it to be notified when it is updated.
|
||||
@@ -122,9 +122,9 @@ IRC group on `irc.freenode.net` called `container-projects`
|
||||
that has been setup.
|
||||
|
||||
For discussions around issues/bugs and features, you can use the github
|
||||
[issues](https://github.com/projectatomic/skopeo/issues)
|
||||
[issues](https://github.com/containers/skopeo/issues)
|
||||
and
|
||||
[PRs](https://github.com/projectatomic/skopeo/pulls)
|
||||
[PRs](https://github.com/containers/skopeo/pulls)
|
||||
tracking system.
|
||||
|
||||
<!--
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,6 +1,6 @@
|
||||
FROM fedora
|
||||
|
||||
RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-go-md2man \
|
||||
RUN dnf -y update && dnf install -y make git golang golang-github-cpuguy83-md2man \
|
||||
# storage deps
|
||||
btrfs-progs-devel \
|
||||
device-mapper-devel \
|
||||
@@ -32,6 +32,8 @@ RUN set -x \
|
||||
RUN set -x \
|
||||
&& export GOPATH=$(mktemp -d) \
|
||||
&& git clone --depth 1 -b v1.5.0-alpha.3 git://github.com/openshift/origin "$GOPATH/src/github.com/openshift/origin" \
|
||||
# The sed edits out a "go < 1.5" check which works incorrectly with go ≥ 1.10. \
|
||||
&& sed -i -e 's/\[\[ "\${go_version\[2]}" < "go1.5" ]]/false/' "$GOPATH/src/github.com/openshift/origin/hack/common.sh" \
|
||||
&& (cd "$GOPATH/src/github.com/openshift/origin" && make clean build && make all WHAT=cmd/dockerregistry) \
|
||||
&& cp -a "$GOPATH/src/github.com/openshift/origin/_output/local/bin/linux"/*/* /usr/local/bin \
|
||||
&& cp "$GOPATH/src/github.com/openshift/origin/images/dockerregistry/config.yml" /atomic-registry-config.yml \
|
||||
@@ -40,8 +42,9 @@ RUN set -x \
|
||||
|
||||
ENV GOPATH /usr/share/gocode:/go
|
||||
ENV PATH $GOPATH/bin:/usr/share/gocode/bin:$PATH
|
||||
RUN go get github.com/golang/lint/golint
|
||||
WORKDIR /go/src/github.com/projectatomic/skopeo
|
||||
COPY . /go/src/github.com/projectatomic/skopeo
|
||||
RUN go version
|
||||
RUN go get golang.org/x/lint/golint
|
||||
WORKDIR /go/src/github.com/containers/skopeo
|
||||
COPY . /go/src/github.com/containers/skopeo
|
||||
|
||||
#ENTRYPOINT ["hack/dind"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:17.04
|
||||
FROM ubuntu:17.10
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
golang \
|
||||
@@ -11,4 +11,4 @@ RUN apt-get update && apt-get install -y \
|
||||
libostree-dev
|
||||
|
||||
ENV GOPATH=/
|
||||
WORKDIR /src/github.com/projectatomic/skopeo
|
||||
WORKDIR /src/github.com/containers/skopeo
|
||||
|
||||
43
Makefile
43
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: all binary build-container build-local clean install install-binary install-completions shell test-integration
|
||||
.PHONY: all binary build-container docs build-local clean install install-binary install-completions shell test-integration vendor
|
||||
|
||||
export GO15VENDOREXPERIMENT=1
|
||||
|
||||
@@ -24,6 +24,7 @@ SIGSTOREDIR=${DESTDIR}/var/lib/atomic/sigstore
|
||||
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
|
||||
GO_MD2MAN ?= go-md2man
|
||||
GO ?= go
|
||||
CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker)
|
||||
|
||||
ifeq ($(DEBUG), 1)
|
||||
override GOGCFLAGS += -N -l
|
||||
@@ -34,17 +35,17 @@ ifeq ($(shell go env GOOS), linux)
|
||||
endif
|
||||
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||
DOCKER_IMAGE := skopeo-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
IMAGE := skopeo-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||
# set env like gobuildtag?
|
||||
DOCKER_FLAGS := docker run --rm -i #$(DOCKER_ENVS)
|
||||
CONTAINER_CMD := ${CONTAINER_RUNTIME} run --rm -i -e TESTFLAGS="$(TESTFLAGS)" #$(CONTAINER_ENVS)
|
||||
# if this session isn't interactive, then we don't want to allocate a
|
||||
# TTY, which would fail, but if it is interactive, we do want to attach
|
||||
# so that the user can send e.g. ^C through.
|
||||
INTERACTIVE := $(shell [ -t 0 ] && echo 1 || echo 0)
|
||||
ifeq ($(INTERACTIVE), 1)
|
||||
DOCKER_FLAGS += -t
|
||||
CONTAINER_CMD += -t
|
||||
endif
|
||||
DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||
CONTAINER_RUN := $(CONTAINER_CMD) "$(IMAGE)"
|
||||
|
||||
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
|
||||
|
||||
@@ -55,25 +56,29 @@ LIBDM_BUILD_TAG = $(shell hack/libdm_tag.sh)
|
||||
LOCAL_BUILD_TAGS = $(BTRFS_BUILD_TAG) $(LIBDM_BUILD_TAG) $(DARWIN_BUILD_TAG)
|
||||
BUILDTAGS += $(LOCAL_BUILD_TAGS)
|
||||
|
||||
ifeq ($(DISABLE_CGO), 1)
|
||||
override BUILDTAGS = containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp
|
||||
endif
|
||||
|
||||
# make all DEBUG=1
|
||||
# Note: Uses the -N -l go compiler options to disable compiler optimizations
|
||||
# and inlining. Using these build options allows you to subsequently
|
||||
# use source debugging tools like delve.
|
||||
all: binary docs
|
||||
|
||||
# Build a docker image (skopeobuild) that has everything we need to build.
|
||||
# Build a container image (skopeobuild) that has everything we need to build.
|
||||
# Then do the build and the output (skopeo) should appear in current dir
|
||||
binary: cmd/skopeo
|
||||
docker build ${DOCKER_BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
docker run --rm --security-opt label:disable -v $$(pwd):/src/github.com/projectatomic/skopeo \
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
${CONTAINER_RUNTIME} run --rm --security-opt label:disable -v $$(pwd):/src/github.com/containers/skopeo \
|
||||
skopeobuildimage make binary-local $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
binary-static: cmd/skopeo
|
||||
docker build ${DOCKER_BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
docker run --rm --security-opt label:disable -v $$(pwd):/src/github.com/projectatomic/skopeo \
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -f Dockerfile.build -t skopeobuildimage .
|
||||
${CONTAINER_RUNTIME} run --rm --security-opt label:disable -v $$(pwd):/src/github.com/containers/skopeo \
|
||||
skopeobuildimage make binary-local-static $(if $(DEBUG),DEBUG=$(DEBUG)) BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
# Build w/o using Docker containers
|
||||
# Build w/o using containers
|
||||
binary-local:
|
||||
$(GPGME_ENV) $(GO) build ${GO_DYN_FLAGS} -ldflags "-X main.gitCommit=${GIT_COMMIT}" -gcflags "$(GOGCFLAGS)" -tags "$(BUILDTAGS)" -o skopeo ./cmd/skopeo
|
||||
|
||||
@@ -81,12 +86,11 @@ binary-local-static:
|
||||
$(GPGME_ENV) $(GO) build -ldflags "-extldflags \"-static\" -X main.gitCommit=${GIT_COMMIT}" -gcflags "$(GOGCFLAGS)" -tags "$(BUILDTAGS)" -o skopeo ./cmd/skopeo
|
||||
|
||||
build-container:
|
||||
docker build ${DOCKER_BUILD_ARGS} -t "$(DOCKER_IMAGE)" .
|
||||
${CONTAINER_RUNTIME} build ${BUILD_ARGS} -t "$(IMAGE)" .
|
||||
|
||||
docs/%.1: docs/%.1.md
|
||||
$(GO_MD2MAN) -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@
|
||||
|
||||
.PHONY: docs
|
||||
docs: $(MANPAGES_MD:%.md=%)
|
||||
|
||||
clean:
|
||||
@@ -112,20 +116,20 @@ install-completions:
|
||||
install -m 644 completions/bash/skopeo ${BASHINSTALLDIR}/skopeo
|
||||
|
||||
shell: build-container
|
||||
$(DOCKER_RUN_DOCKER) bash
|
||||
$(CONTAINER_RUN) bash
|
||||
|
||||
check: validate test-unit test-integration
|
||||
|
||||
# The tests can run out of entropy and block in containers, so replace /dev/random.
|
||||
test-integration: build-container
|
||||
$(DOCKER_RUN_DOCKER) bash -c 'rm -f /dev/random; ln -sf /dev/urandom /dev/random; SKOPEO_CONTAINER_TESTS=1 BUILDTAGS="$(BUILDTAGS)" hack/make.sh test-integration'
|
||||
$(CONTAINER_RUN) bash -c 'rm -f /dev/random; ln -sf /dev/urandom /dev/random; SKOPEO_CONTAINER_TESTS=1 BUILDTAGS="$(BUILDTAGS)" hack/make.sh test-integration'
|
||||
|
||||
test-unit: build-container
|
||||
# Just call (make test unit-local) here instead of worrying about environment differences, e.g. GO15VENDOREXPERIMENT.
|
||||
$(DOCKER_RUN_DOCKER) make test-unit-local BUILDTAGS='$(BUILDTAGS)'
|
||||
$(CONTAINER_RUN) make test-unit-local BUILDTAGS='$(BUILDTAGS)'
|
||||
|
||||
validate: build-container
|
||||
$(DOCKER_RUN_DOCKER) hack/make.sh validate-git-marks validate-gofmt validate-lint validate-vet
|
||||
$(CONTAINER_RUN) hack/make.sh validate-git-marks validate-gofmt validate-lint validate-vet
|
||||
|
||||
# This target is only intended for development, e.g. executing it from an IDE. Use (make test) for CI or pre-release testing.
|
||||
test-all-local: validate-local test-unit-local
|
||||
@@ -134,4 +138,7 @@ validate-local:
|
||||
hack/make.sh validate-git-marks validate-gofmt validate-lint validate-vet
|
||||
|
||||
test-unit-local:
|
||||
$(GPGME_ENV) $(GO) test -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/projectatomic/skopeo/\(integration\|vendor/.*\)$$')
|
||||
$(GPGME_ENV) $(GO) test -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$')
|
||||
|
||||
vendor: vendor.conf
|
||||
vndr -whitelist '^github.com/containers/image/docs/.*'
|
||||
|
||||
49
README.md
49
README.md
@@ -1,18 +1,18 @@
|
||||
skopeo [](https://travis-ci.org/projectatomic/skopeo)
|
||||
skopeo [](https://travis-ci.org/containers/skopeo)
|
||||
=
|
||||
|
||||
<img src="https://cdn.rawgit.com/projectatomic/skopeo/master/docs/skopeo.svg" width="250">
|
||||
<img src="https://cdn.rawgit.com/containers/skopeo/master/docs/skopeo.svg" width="250">
|
||||
|
||||
----
|
||||
|
||||
`skopeo` is a command line utility that performs various operations on container images and image repositories.
|
||||
|
||||
`skopeo` can work with (OCI images)[https://github.com/opencontainers/image-spec] as well as the original Docker v2 images.
|
||||
`skopeo` can work with [OCI images](https://github.com/opencontainers/image-spec) as well as the original Docker v2 images.
|
||||
|
||||
Skopeo works with API V2 registries such as Docker registries, the Atomic registry, private registries, local directories and local OCI-layout directories. Skopeo does not require a daemon to be running to perform these operations which consist of:
|
||||
|
||||
* Copying an image from and to various storage mechanisms.
|
||||
For example you can copy images from one registry to another, without requiring priviledge.
|
||||
For example you can copy images from one registry to another, without requiring privilege.
|
||||
* Inspecting a remote image showing its properties including its layers, without requiring you to pull the image to the host.
|
||||
* Deleting an image from an image repository.
|
||||
* When required by the repository, skopeo can pass the appropriate credentials and certificates for authentication.
|
||||
@@ -149,8 +149,16 @@ $ skopeo copy --src-creds=testuser:testpassword docker://myregistrydomain.com:50
|
||||
If your cli config is found but it doesn't contain the necessary credentials for the queried registry
|
||||
you'll get an error. You can fix this by either logging in (via `docker login`) or providing `--creds` or `--src-creds|--dest-creds`.
|
||||
|
||||
Building
|
||||
|
||||
Obtaining skopeo
|
||||
-
|
||||
`skopeo` may already be packaged in your distribution, for example on Fedora 23 and later you can install it using
|
||||
```sh
|
||||
$ sudo dnf install skopeo
|
||||
```
|
||||
|
||||
Otherwise, read on for building and installing it from source:
|
||||
|
||||
To build the `skopeo` binary you need at least Go 1.5 because it uses the latest `GO15VENDOREXPERIMENT` flag.
|
||||
|
||||
There are two ways to build skopeo: in a container, or locally without a container. Choose the one which better matches your needs and environment.
|
||||
@@ -164,14 +172,15 @@ Building without a container requires a bit more manual work and setup in your e
|
||||
Install the necessary dependencies:
|
||||
```sh
|
||||
Fedora$ sudo dnf install gpgme-devel libassuan-devel btrfs-progs-devel device-mapper-devel ostree-devel
|
||||
Ubuntu$ sudo apt install libgpgme-dev libassuan-dev btrfs-progs libdevmapper-dev libostree-dev
|
||||
macOS$ brew install gpgme
|
||||
```
|
||||
|
||||
Make sure to clone this repository in your `GOPATH` - otherwise compilation fails.
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/projectatomic/skopeo $GOPATH/src/github.com/projectatomic/skopeo
|
||||
$ cd $GOPATH/src/github.com/projectatomic/skopeo && make binary-local
|
||||
$ git clone https://github.com/containers/skopeo $GOPATH/src/github.com/containers/skopeo
|
||||
$ cd $GOPATH/src/github.com/containers/skopeo && make binary-local
|
||||
```
|
||||
|
||||
### Building in a container
|
||||
@@ -183,6 +192,12 @@ Building in a container is simpler, but more restrictive:
|
||||
$ make binary # Or (make all) to also build documentation, see below.
|
||||
```
|
||||
|
||||
To build a pure-Go static binary (disables ostree, devicemapper, btrfs, and gpgme):
|
||||
|
||||
```sh
|
||||
$ make binary-static DISABLE_CGO=1
|
||||
```
|
||||
|
||||
### Building documentation
|
||||
To build the manual you will need go-md2man.
|
||||
```sh
|
||||
@@ -194,16 +209,12 @@ Then
|
||||
$ make docs
|
||||
```
|
||||
|
||||
Installing
|
||||
-
|
||||
If you built from source:
|
||||
### Installation
|
||||
Finally, after the binary and documentation is built:
|
||||
```sh
|
||||
$ sudo make install
|
||||
```
|
||||
`skopeo` is also available from Fedora 23 (and later):
|
||||
```sh
|
||||
$ sudo dnf install skopeo
|
||||
```
|
||||
|
||||
TODO
|
||||
-
|
||||
- list all images on registry?
|
||||
@@ -220,30 +231,30 @@ CONTRIBUTING
|
||||
|
||||
### Dependencies management
|
||||
|
||||
`skopeo` uses [`vndr`](https://github.com/LK4D4/vndr) for dependencies management.
|
||||
Make sure [`vndr`](https://github.com/LK4D4/vndr) is installed.
|
||||
|
||||
In order to add a new dependency to this project:
|
||||
|
||||
- add a new line to `vendor.conf` according to `vndr` rules (e.g. `github.com/pkg/errors master`)
|
||||
- run `vndr github.com/pkg/errors`
|
||||
- run `make vendor`
|
||||
|
||||
In order to update an existing dependency:
|
||||
|
||||
- update the relevant dependency line in `vendor.conf`
|
||||
- run `vndr github.com/pkg/errors`
|
||||
- run `make vendor`
|
||||
|
||||
When new PRs for [containers/image](https://github.com/containers/image) break `skopeo` (i.e. `containers/image` tests fail in `make test-skopeo`):
|
||||
|
||||
- create out a new branch in your `skopeo` checkout and switch to it
|
||||
- update `vendor.conf`. Find out the `containers/image` dependency; update it to vendor from your own branch and your own repository fork (e.g. `github.com/containers/image my-branch https://github.com/runcom/image`)
|
||||
- run `vndr github.com/containers/image`
|
||||
- run `make vendor`
|
||||
- make any other necessary changes in the skopeo repo (e.g. add other dependencies now requied by `containers/image`, or update skopeo for changed `containers/image` API)
|
||||
- optionally add new integration tests to the skopeo repo
|
||||
- submit the resulting branch as a skopeo PR, marked “DO NOT MERGE”
|
||||
- iterate until tests pass and the PR is reviewed
|
||||
- then the original `containers/image` PR can be merged, disregarding its `make test-skopeo` failure
|
||||
- as soon as possible after that, in the skopeo PR, restore the `containers/image` line in `vendor.conf` to use `containers/image:master`
|
||||
- run `vndr github.com/containers/image`
|
||||
- run `make vendor`
|
||||
- update the skopeo PR with the result, drop the “DO NOT MERGE” marking
|
||||
- after tests complete succcesfully again, merge the skopeo PR
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/copy"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/transports/alltransports"
|
||||
@@ -30,36 +31,37 @@ func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.Sys
|
||||
return sourceCtx, destinationCtx, nil
|
||||
}
|
||||
|
||||
func copyHandler(context *cli.Context) error {
|
||||
if len(context.Args()) != 2 {
|
||||
return errors.New("Usage: copy source destination")
|
||||
func copyHandler(c *cli.Context) error {
|
||||
if len(c.Args()) != 2 {
|
||||
cli.ShowCommandHelp(c, "copy")
|
||||
return errors.New("Exactly two arguments expected")
|
||||
}
|
||||
|
||||
policyContext, err := getPolicyContext(context)
|
||||
policyContext, err := getPolicyContext(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading trust policy: %v", err)
|
||||
}
|
||||
defer policyContext.Destroy()
|
||||
|
||||
srcRef, err := alltransports.ParseImageName(context.Args()[0])
|
||||
srcRef, err := alltransports.ParseImageName(c.Args()[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid source name %s: %v", context.Args()[0], err)
|
||||
return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
|
||||
}
|
||||
destRef, err := alltransports.ParseImageName(context.Args()[1])
|
||||
destRef, err := alltransports.ParseImageName(c.Args()[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid destination name %s: %v", context.Args()[1], err)
|
||||
return fmt.Errorf("Invalid destination name %s: %v", c.Args()[1], err)
|
||||
}
|
||||
signBy := context.String("sign-by")
|
||||
removeSignatures := context.Bool("remove-signatures")
|
||||
signBy := c.String("sign-by")
|
||||
removeSignatures := c.Bool("remove-signatures")
|
||||
|
||||
sourceCtx, destinationCtx, err := contextsFromGlobalOptions(context)
|
||||
sourceCtx, destinationCtx, err := contextsFromGlobalOptions(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var manifestType string
|
||||
if context.IsSet("format") {
|
||||
switch context.String("format") {
|
||||
if c.IsSet("format") {
|
||||
switch c.String("format") {
|
||||
case "oci":
|
||||
manifestType = imgspecv1.MediaTypeImageManifest
|
||||
case "v2s1":
|
||||
@@ -67,11 +69,28 @@ func copyHandler(context *cli.Context) error {
|
||||
case "v2s2":
|
||||
manifestType = manifest.DockerV2Schema2MediaType
|
||||
default:
|
||||
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", context.String("format"))
|
||||
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
|
||||
}
|
||||
}
|
||||
|
||||
return copy.Image(policyContext, destRef, srcRef, ©.Options{
|
||||
if c.IsSet("additional-tag") {
|
||||
for _, image := range c.StringSlice("additional-tag") {
|
||||
ref, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing additional-tag '%s': %v", image, err)
|
||||
}
|
||||
namedTagged, isNamedTagged := ref.(reference.NamedTagged)
|
||||
if !isNamedTagged {
|
||||
return fmt.Errorf("additional-tag '%s' must be a tagged reference", image)
|
||||
}
|
||||
destinationCtx.DockerArchiveAdditionalTags = append(destinationCtx.DockerArchiveAdditionalTags, namedTagged)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
|
||||
defer cancel()
|
||||
|
||||
_, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{
|
||||
RemoveSignatures: removeSignatures,
|
||||
SignBy: signBy,
|
||||
ReportWriter: os.Stdout,
|
||||
@@ -79,6 +98,7 @@ func copyHandler(context *cli.Context) error {
|
||||
DestinationCtx: destinationCtx,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
var copyCmd = cli.Command{
|
||||
@@ -97,6 +117,10 @@ var copyCmd = cli.Command{
|
||||
Action: copyHandler,
|
||||
// FIXME: Do we need to namespace the GPG aspect?
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "additional-tag",
|
||||
Usage: "additional tags (supports docker-archive)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "authfile",
|
||||
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
|
||||
@@ -122,20 +146,20 @@ var copyCmd = cli.Command{
|
||||
cli.StringFlag{
|
||||
Name: "src-cert-dir",
|
||||
Value: "",
|
||||
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry",
|
||||
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "src-tls-verify",
|
||||
Usage: "require HTTPS and verify certificates when talking to the container source registry (defaults to true)",
|
||||
Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dest-cert-dir",
|
||||
Value: "",
|
||||
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry",
|
||||
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "dest-tls-verify",
|
||||
Usage: "require HTTPS and verify certificates when talking to the container destination registry (defaults to true)",
|
||||
Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dest-ostree-tmp-dir",
|
||||
@@ -160,5 +184,15 @@ var copyCmd = cli.Command{
|
||||
Name: "dest-compress",
|
||||
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "src-daemon-host",
|
||||
Value: "",
|
||||
Usage: "use docker daemon host at `HOST` (docker-daemon sources only)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "dest-daemon-host",
|
||||
Value: "",
|
||||
Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -10,21 +10,24 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func deleteHandler(context *cli.Context) error {
|
||||
if len(context.Args()) != 1 {
|
||||
func deleteHandler(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
return errors.New("Usage: delete imageReference")
|
||||
}
|
||||
|
||||
ref, err := alltransports.ParseImageName(context.Args()[0])
|
||||
ref, err := alltransports.ParseImageName(c.Args()[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid source name %s: %v", context.Args()[0], err)
|
||||
return fmt.Errorf("Invalid source name %s: %v", c.Args()[0], err)
|
||||
}
|
||||
|
||||
ctx, err := contextFromGlobalOptions(context, "")
|
||||
sys, err := contextFromGlobalOptions(c, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ref.DeleteImage(ctx)
|
||||
|
||||
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
|
||||
defer cancel()
|
||||
return ref.DeleteImage(ctx, sys)
|
||||
}
|
||||
|
||||
var deleteCmd = cli.Command{
|
||||
|
||||
@@ -21,7 +21,7 @@ type inspectOutput struct {
|
||||
Tag string `json:",omitempty"`
|
||||
Digest digest.Digest
|
||||
RepoTags []string
|
||||
Created time.Time
|
||||
Created *time.Time
|
||||
DockerVersion string
|
||||
Labels map[string]string
|
||||
Architecture string
|
||||
@@ -66,7 +66,10 @@ var inspectCmd = cli.Command{
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) (retErr error) {
|
||||
img, err := parseImage(c)
|
||||
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
|
||||
defer cancel()
|
||||
|
||||
img, err := parseImage(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -77,7 +80,7 @@ var inspectCmd = cli.Command{
|
||||
}
|
||||
}()
|
||||
|
||||
rawManifest, _, err := img.Manifest()
|
||||
rawManifest, _, err := img.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -88,15 +91,15 @@ var inspectCmd = cli.Command{
|
||||
}
|
||||
return nil
|
||||
}
|
||||
imgInspect, err := img.Inspect()
|
||||
imgInspect, err := img.Inspect(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputData := inspectOutput{
|
||||
Name: "", // Possibly overridden for a docker.Image.
|
||||
Name: "", // Set below if DockerReference() is known
|
||||
Tag: imgInspect.Tag,
|
||||
// Digest is set below.
|
||||
RepoTags: []string{}, // Possibly overriden for a docker.Image.
|
||||
RepoTags: []string{}, // Possibly overriden for docker.Transport.
|
||||
Created: imgInspect.Created,
|
||||
DockerVersion: imgInspect.DockerVersion,
|
||||
Labels: imgInspect.Labels,
|
||||
@@ -108,9 +111,15 @@ var inspectCmd = cli.Command{
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error computing manifest digest: %v", err)
|
||||
}
|
||||
if dockerImg, ok := img.(*docker.Image); ok {
|
||||
outputData.Name = dockerImg.SourceRefFullName()
|
||||
outputData.RepoTags, err = dockerImg.GetRepositoryTags()
|
||||
if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
|
||||
outputData.Name = dockerRef.Name()
|
||||
}
|
||||
if img.Reference().Transport() == docker.Transport {
|
||||
sys, err := contextFromGlobalOptions(c, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference())
|
||||
if err != nil {
|
||||
// some registries may decide to block the "list all tags" endpoint
|
||||
// gracefully allow the inspect to continue in this case. Currently
|
||||
|
||||
@@ -24,15 +24,19 @@ var layersCmd = cli.Command{
|
||||
if c.NArg() == 0 {
|
||||
return errors.New("Usage: layers imageReference [layer...]")
|
||||
}
|
||||
ctx, err := contextFromGlobalOptions(c, "")
|
||||
|
||||
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
|
||||
defer cancel()
|
||||
|
||||
sys, err := contextFromGlobalOptions(c, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawSource, err := parseImageSource(c, c.Args()[0])
|
||||
rawSource, err := parseImageSource(ctx, c, c.Args()[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := image.FromSource(ctx, rawSource)
|
||||
src, err := image.FromSource(ctx, sys, rawSource)
|
||||
if err != nil {
|
||||
if closeErr := rawSource.Close(); closeErr != nil {
|
||||
return errors.Wrapf(err, " (close error: %v)", closeErr)
|
||||
@@ -46,7 +50,11 @@ var layersCmd = cli.Command{
|
||||
}
|
||||
}()
|
||||
|
||||
var blobDigests []digest.Digest
|
||||
type blobDigest struct {
|
||||
digest digest.Digest
|
||||
isConfig bool
|
||||
}
|
||||
var blobDigests []blobDigest
|
||||
for _, dString := range c.Args().Tail() {
|
||||
if !strings.HasPrefix(dString, "sha256:") {
|
||||
dString = "sha256:" + dString
|
||||
@@ -55,7 +63,7 @@ var layersCmd = cli.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blobDigests = append(blobDigests, d)
|
||||
blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false})
|
||||
}
|
||||
|
||||
if len(blobDigests) == 0 {
|
||||
@@ -63,13 +71,13 @@ var layersCmd = cli.Command{
|
||||
seenLayers := map[digest.Digest]struct{}{}
|
||||
for _, info := range layers {
|
||||
if _, ok := seenLayers[info.Digest]; !ok {
|
||||
blobDigests = append(blobDigests, info.Digest)
|
||||
blobDigests = append(blobDigests, blobDigest{digest: info.Digest, isConfig: false})
|
||||
seenLayers[info.Digest] = struct{}{}
|
||||
}
|
||||
}
|
||||
configInfo := src.ConfigInfo()
|
||||
if configInfo.Digest != "" {
|
||||
blobDigests = append(blobDigests, configInfo.Digest)
|
||||
blobDigests = append(blobDigests, blobDigest{digest: configInfo.Digest, isConfig: true})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +89,7 @@ var layersCmd = cli.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest, err := tmpDirRef.NewImageDestination(nil)
|
||||
dest, err := tmpDirRef.NewImageDestination(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,12 +100,12 @@ var layersCmd = cli.Command{
|
||||
}
|
||||
}()
|
||||
|
||||
for _, digest := range blobDigests {
|
||||
r, blobSize, err := rawSource.GetBlob(types.BlobInfo{Digest: digest, Size: -1})
|
||||
for _, bd := range blobDigests {
|
||||
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := dest.PutBlob(r, types.BlobInfo{Digest: digest, Size: blobSize}); err != nil {
|
||||
if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, bd.isConfig); err != nil {
|
||||
if closeErr := r.Close(); closeErr != nil {
|
||||
return errors.Wrapf(err, " (close error: %v)", closeErr)
|
||||
}
|
||||
@@ -105,14 +113,14 @@ var layersCmd = cli.Command{
|
||||
}
|
||||
}
|
||||
|
||||
manifest, _, err := src.Manifest()
|
||||
manifest, _, err := src.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dest.PutManifest(manifest); err != nil {
|
||||
if err := dest.PutManifest(ctx, manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dest.Commit()
|
||||
return dest.Commit(ctx)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/signature"
|
||||
"github.com/containers/skopeo/version"
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/projectatomic/skopeo/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
@@ -60,6 +60,10 @@ func createApp() *cli.App {
|
||||
Value: "",
|
||||
Usage: "use `OS` instead of the running OS for choosing images",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "command-timeout",
|
||||
Usage: "timeout for the command execution",
|
||||
},
|
||||
}
|
||||
app.Before = func(c *cli.Context) error {
|
||||
if c.GlobalBool("debug") {
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func standaloneSign(context *cli.Context) error {
|
||||
outputFile := context.String("output")
|
||||
if len(context.Args()) != 3 || outputFile == "" {
|
||||
func standaloneSign(c *cli.Context) error {
|
||||
outputFile := c.String("output")
|
||||
if len(c.Args()) != 3 || outputFile == "" {
|
||||
return errors.New("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature")
|
||||
}
|
||||
manifestPath := context.Args()[0]
|
||||
dockerReference := context.Args()[1]
|
||||
fingerprint := context.Args()[2]
|
||||
manifestPath := c.Args()[0]
|
||||
dockerReference := c.Args()[1]
|
||||
fingerprint := c.Args()[2]
|
||||
|
||||
manifest, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
@@ -53,14 +53,14 @@ var standaloneSignCmd = cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func standaloneVerify(context *cli.Context) error {
|
||||
if len(context.Args()) != 4 {
|
||||
func standaloneVerify(c *cli.Context) error {
|
||||
if len(c.Args()) != 4 {
|
||||
return errors.New("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature")
|
||||
}
|
||||
manifestPath := context.Args()[0]
|
||||
expectedDockerReference := context.Args()[1]
|
||||
expectedFingerprint := context.Args()[2]
|
||||
signaturePath := context.Args()[3]
|
||||
manifestPath := c.Args()[0]
|
||||
expectedDockerReference := c.Args()[1]
|
||||
expectedFingerprint := c.Args()[2]
|
||||
signaturePath := c.Args()[3]
|
||||
|
||||
unverifiedManifest, err := ioutil.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
@@ -81,7 +81,7 @@ func standaloneVerify(context *cli.Context) error {
|
||||
return fmt.Errorf("Error verifying signature: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(context.App.Writer, "Signature verified, digest %s\n", sig.DockerManifestDigest)
|
||||
fmt.Fprintf(c.App.Writer, "Signature verified, digest %s\n", sig.DockerManifestDigest)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@ var standaloneVerifyCmd = cli.Command{
|
||||
Action: standaloneVerify,
|
||||
}
|
||||
|
||||
func untrustedSignatureDump(context *cli.Context) error {
|
||||
if len(context.Args()) != 1 {
|
||||
func untrustedSignatureDump(c *cli.Context) error {
|
||||
if len(c.Args()) != 1 {
|
||||
return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature")
|
||||
}
|
||||
untrustedSignaturePath := context.Args()[0]
|
||||
untrustedSignaturePath := c.Args()[0]
|
||||
|
||||
untrustedSignature, err := ioutil.ReadFile(untrustedSignaturePath)
|
||||
if err != nil {
|
||||
@@ -111,7 +111,7 @@ func untrustedSignatureDump(context *cli.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(context.App.Writer, string(untrustedOut))
|
||||
fmt.Fprintln(c.App.Writer, string(untrustedOut))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
@@ -17,11 +18,14 @@ func contextFromGlobalOptions(c *cli.Context, flagPrefix string) (*types.SystemC
|
||||
DockerCertPath: c.String(flagPrefix + "cert-dir"),
|
||||
// DEPRECATED: keep this here for backward compatibility, but override
|
||||
// them if per subcommand flags are provided (see below).
|
||||
DockerInsecureSkipTLSVerify: !c.GlobalBoolT("tls-verify"),
|
||||
OSTreeTmpDirPath: c.String(flagPrefix + "ostree-tmp-dir"),
|
||||
OCISharedBlobDirPath: c.String(flagPrefix + "shared-blob-dir"),
|
||||
DirForceCompress: c.Bool(flagPrefix + "compress"),
|
||||
AuthFilePath: c.String("authfile"),
|
||||
DockerInsecureSkipTLSVerify: !c.GlobalBoolT("tls-verify"),
|
||||
OSTreeTmpDirPath: c.String(flagPrefix + "ostree-tmp-dir"),
|
||||
OCISharedBlobDirPath: c.String(flagPrefix + "shared-blob-dir"),
|
||||
DirForceCompress: c.Bool(flagPrefix + "compress"),
|
||||
AuthFilePath: c.String("authfile"),
|
||||
DockerDaemonHost: c.String(flagPrefix + "daemon-host"),
|
||||
DockerDaemonCertPath: c.String(flagPrefix + "cert-dir"),
|
||||
DockerDaemonInsecureSkipTLSVerify: !c.BoolT(flagPrefix + "tls-verify"),
|
||||
}
|
||||
if c.IsSet(flagPrefix + "tls-verify") {
|
||||
ctx.DockerInsecureSkipTLSVerify = !c.BoolT(flagPrefix + "tls-verify")
|
||||
@@ -36,6 +40,15 @@ func contextFromGlobalOptions(c *cli.Context, flagPrefix string) (*types.SystemC
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func commandTimeoutContextFromGlobalOptions(c *cli.Context) (context.Context, context.CancelFunc) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc = func() {}
|
||||
if c.GlobalDuration("command-timeout") > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, c.GlobalDuration("command-timeout"))
|
||||
}
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
func parseCreds(creds string) (string, string, error) {
|
||||
if creds == "" {
|
||||
return "", "", errors.New("credentials can't be empty")
|
||||
@@ -63,29 +76,29 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) {
|
||||
|
||||
// parseImage converts image URL-like string to an initialized handler for that image.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
func parseImage(c *cli.Context) (types.ImageCloser, error) {
|
||||
func parseImage(ctx context.Context, c *cli.Context) (types.ImageCloser, error) {
|
||||
imgName := c.Args().First()
|
||||
ref, err := alltransports.ParseImageName(imgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := contextFromGlobalOptions(c, "")
|
||||
sys, err := contextFromGlobalOptions(c, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ref.NewImage(ctx)
|
||||
return ref.NewImage(ctx, sys)
|
||||
}
|
||||
|
||||
// parseImageSource converts image URL-like string to an ImageSource.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func parseImageSource(c *cli.Context, name string) (types.ImageSource, error) {
|
||||
func parseImageSource(ctx context.Context, c *cli.Context, name string) (types.ImageSource, error) {
|
||||
ref, err := alltransports.ParseImageName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, err := contextFromGlobalOptions(c, "")
|
||||
sys, err := contextFromGlobalOptions(c, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ref.NewImageSource(ctx)
|
||||
return ref.NewImageSource(ctx, sys)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ _skopeo_copy() {
|
||||
--dest-cert-dir
|
||||
--dest-ostree-tmp-dir
|
||||
--dest-tls-verify
|
||||
--src-daemon-host
|
||||
--dest-daemon-host
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -108,6 +110,7 @@ _skopeo_skopeo() {
|
||||
--registries.d
|
||||
--override-arch
|
||||
--override-os
|
||||
--command-timeout
|
||||
"
|
||||
local boolean_options="
|
||||
--insecure-policy
|
||||
|
||||
60
contrib/containers-storage.conf.5.md
Normal file
60
contrib/containers-storage.conf.5.md
Normal file
@@ -0,0 +1,60 @@
|
||||
% storage.conf(5) Container Storage Configuration File
|
||||
% Dan Walsh
|
||||
% May 2017
|
||||
|
||||
# NAME
|
||||
storage.conf - Syntax of Container Storage configuration file
|
||||
|
||||
# DESCRIPTION
|
||||
The STORAGE configuration file specifies all of the available container storage options
|
||||
for tools using shared container storage.
|
||||
|
||||
# FORMAT
|
||||
The [TOML format][toml] is used as the encoding of the configuration file.
|
||||
Every option and subtable listed here is nested under a global "storage" table.
|
||||
No bare options are used. The format of TOML can be simplified to:
|
||||
|
||||
[table]
|
||||
option = value
|
||||
|
||||
[table.subtable1]
|
||||
option = value
|
||||
|
||||
[table.subtable2]
|
||||
option = value
|
||||
|
||||
## STORAGE TABLE
|
||||
|
||||
The `storage` table supports the following options:
|
||||
|
||||
**graphroot**=""
|
||||
container storage graph dir (default: "/var/lib/containers/storage")
|
||||
Default directory to store all writable content created by container storage programs.
|
||||
|
||||
**runroot**=""
|
||||
container storage run dir (default: "/var/run/containers/storage")
|
||||
Default directory to store all temporary writable content created by container storage programs.
|
||||
|
||||
**driver**=""
|
||||
container storage driver (default is "overlay")
|
||||
Default Copy On Write (COW) container storage driver.
|
||||
|
||||
### STORAGE OPTIONS TABLE
|
||||
|
||||
The `storage.options` table supports the following options:
|
||||
|
||||
**additionalimagestores**=[]
|
||||
Paths to additional container image stores. Usually these are read-only and stored on remote network shares.
|
||||
|
||||
**size**=""
|
||||
Maximum size of a container image. Default is 10GB. This flag can be used to set quota
|
||||
on the size of container images.
|
||||
|
||||
**override_kernel_check**=""
|
||||
Tell storage drivers to ignore kernel version checks. Some storage drivers assume that if a kernel is too
|
||||
old, the driver is not supported. But for kernels that have had the drivers backported, this flag
|
||||
allows users to override the checks.
|
||||
|
||||
# HISTORY
|
||||
May 2017, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
||||
Format copied from crio.conf man page created by Aleksa Sarai <asarai@suse.de>
|
||||
28
contrib/storage.conf
Normal file
28
contrib/storage.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
# storage.conf is the configuration file for all tools
|
||||
# that share the containers/storage libraries
|
||||
# See man 5 containers-storage.conf for more information
|
||||
|
||||
# The "container storage" table contains all of the server options.
|
||||
[storage]
|
||||
|
||||
# Default Storage Driver
|
||||
driver = "overlay"
|
||||
|
||||
# Temporary storage location
|
||||
runroot = "/var/run/containers/storage"
|
||||
|
||||
# Primary read-write location of container storage
|
||||
graphroot = "/var/lib/containers/storage"
|
||||
|
||||
[storage.options]
|
||||
# AdditionalImageStores is used to pass paths to additional read-only image stores
|
||||
# Must be comma separated list.
|
||||
additionalimagestores = [
|
||||
]
|
||||
|
||||
# Size is used to set a maximum size of the container image. Only supported by
|
||||
# certain container storage drivers (currently overlay, zfs, vfs, btrfs)
|
||||
size = ""
|
||||
|
||||
# OverrideKernelCheck tells the driver to ignore kernel checks based on kernel version
|
||||
override_kernel_check = "true"
|
||||
@@ -59,6 +59,8 @@ Most commands refer to container images, using a _transport_`:`_details_ format.
|
||||
|
||||
**--override-os** _OS_ Use _OS_ instead of the running OS for choosing images.
|
||||
|
||||
**--command-timeout** _duration_ Timeout for the command execution.
|
||||
|
||||
**--help**|**-h** Show help
|
||||
|
||||
**--version**|**-v** print the version number
|
||||
@@ -93,15 +95,19 @@ Uses the system's trust policy to validate images, rejects images not trusted by
|
||||
|
||||
**--dest-creds** _username[:password]_ for accessing the destination registry
|
||||
|
||||
**--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry
|
||||
**--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry or daemon
|
||||
|
||||
**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container source registry (defaults to true)
|
||||
**--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container source registry or daemon (defaults to true)
|
||||
|
||||
**--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry
|
||||
**--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry or daemon
|
||||
|
||||
**--dest-ostree-tmp-dir** _path_ Directory to use for OSTree temporary files.
|
||||
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container destination registry (defaults to true)
|
||||
**--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container destination registry or daemon (defaults to true)
|
||||
|
||||
**--src-daemon-host** _host_ Copy from docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`).
|
||||
|
||||
**--dest-daemon-host** _host_ Copy to docker daemon at _host_. If _host_ starts with `tcp://`, HTTPS is enabled by default. To use plain HTTP, use the form `http://` (default is `unix:///var/run/docker.sock`).
|
||||
|
||||
Existing signatures, if any, are preserved as well.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ set -e
|
||||
#
|
||||
# Requirements:
|
||||
# - The current directory should be a checkout of the skopeo source code
|
||||
# (https://github.com/projectatomic/skopeo). Whatever version is checked out
|
||||
# (https://github.com/containers/skopeo). Whatever version is checked out
|
||||
# will be built.
|
||||
# - The script is intended to be run inside the docker container specified
|
||||
# in the Dockerfile at the root of the source. In other words:
|
||||
@@ -19,7 +19,7 @@ set -e
|
||||
|
||||
set -o pipefail
|
||||
|
||||
export SKOPEO_PKG='github.com/projectatomic/skopeo'
|
||||
export SKOPEO_PKG='github.com/containers/skopeo'
|
||||
export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
export MAKEDIR="$SCRIPTDIR/make"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then
|
||||
# this is kind of an expensive check, so let's not do this twice if we
|
||||
# are running more than one validate bundlescript
|
||||
|
||||
VALIDATE_REPO='https://github.com/projectatomic/skopeo.git'
|
||||
VALIDATE_REPO='https://github.com/containers/skopeo.git'
|
||||
VALIDATE_BRANCH='master'
|
||||
|
||||
if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
source "$(dirname "$BASH_SOURCE")/.validate"
|
||||
errors=$(go vet $(go list -e ./... | grep -v "$SKOPEO_PKG"/vendor))
|
||||
|
||||
IFS=$'\n'
|
||||
files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) )
|
||||
unset IFS
|
||||
|
||||
errors=()
|
||||
for f in "${files[@]}"; do
|
||||
failedVet=$(go vet "$f")
|
||||
if [ "$failedVet" ]; then
|
||||
errors+=( "$failedVet" )
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
if [ ${#errors[@]} -eq 0 ]; then
|
||||
if [ -z "$errors" ]; then
|
||||
echo 'Congratulations! All Go source files have been vetted.'
|
||||
else
|
||||
{
|
||||
echo "Errors from go vet:"
|
||||
for err in "${errors[@]}"; do
|
||||
echo " - $err"
|
||||
done
|
||||
echo "$errors"
|
||||
echo
|
||||
echo 'Please fix the above errors. You can test via "go vet" and commit the result.'
|
||||
echo
|
||||
|
||||
@@ -4,13 +4,14 @@ set -e
|
||||
export GOPATH=$(pwd)/_gopath
|
||||
export PATH=$GOPATH/bin:$PATH
|
||||
|
||||
_projectatomic="${GOPATH}/src/github.com/projectatomic"
|
||||
mkdir -vp ${_projectatomic}
|
||||
ln -vsf $(pwd) ${_projectatomic}/skopeo
|
||||
_containers="${GOPATH}/src/github.com/containers"
|
||||
mkdir -vp ${_containers}
|
||||
ln -vsf $(pwd) ${_containers}/skopeo
|
||||
|
||||
go get -u github.com/cpuguy83/go-md2man github.com/golang/lint/golint
|
||||
go version
|
||||
go get -u github.com/cpuguy83/go-md2man golang.org/x/lint/golint
|
||||
|
||||
cd ${_projectatomic}/skopeo
|
||||
cd ${_containers}/skopeo
|
||||
make validate-local test-unit-local binary-local
|
||||
sudo make install
|
||||
skopeo -v
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This file is just wrapper around vndr (github.com/LK4D4/vndr) tool.
|
||||
# For updating dependencies you should change `vendor.conf` file in root of the
|
||||
# project. Please refer to https://github.com/LK4D4/vndr/blob/master/README.md for
|
||||
# vndr usage.
|
||||
|
||||
set -e
|
||||
|
||||
if ! hash vndr; then
|
||||
echo "Please install vndr with \"go get github.com/LK4D4/vndr\" and put it in your \$GOPATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
vndr "$@"
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/containers/skopeo/version"
|
||||
"github.com/go-check/check"
|
||||
"github.com/projectatomic/skopeo/version"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/signature"
|
||||
"github.com/go-check/check"
|
||||
"github.com/opencontainers/go-digest"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/image-tools/image"
|
||||
)
|
||||
@@ -64,7 +64,7 @@ func (s *CopySuite) SetUpSuite(c *check.C) {
|
||||
os.Setenv("GNUPGHOME", s.gpgHome)
|
||||
|
||||
for _, key := range []string{"personal", "official"} {
|
||||
batchInput := fmt.Sprintf("Key-Type: RSA\nName-Real: Test key - %s\nName-email: %s@example.com\n%%commit\n",
|
||||
batchInput := fmt.Sprintf("Key-Type: RSA\nName-Real: Test key - %s\nName-email: %s@example.com\n%%no-protection\n%%commit\n",
|
||||
key, key)
|
||||
runCommandWithInput(c, batchInput, gpgBinary, "--batch", "--gen-key")
|
||||
|
||||
@@ -154,7 +154,10 @@ func (s *CopySuite) TestCopySimple(c *check.C) {
|
||||
// docker v2s2 -> OCI image layout without image name
|
||||
ociDest = "busybox-latest-noimage"
|
||||
defer os.RemoveAll(ociDest)
|
||||
assertSkopeoFails(c, ".*Error initializing destination oci:busybox-latest-noimage:: cannot save image with empty image.ref.name.*", "copy", "docker://busybox:latest", "oci:"+ociDest)
|
||||
assertSkopeoSucceeds(c, "", "copy", "docker://busybox:latest", "oci:"+ociDest)
|
||||
_, err = os.Stat(ociDest)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
}
|
||||
|
||||
// Check whether dir: images in dir1 and dir2 are equal, ignoring schema1 signatures.
|
||||
@@ -379,7 +382,7 @@ func (s *CopySuite) TestCopyDirSignatures(c *check.C) {
|
||||
|
||||
// Compression during copy
|
||||
func (s *CopySuite) TestCopyCompression(c *check.C) {
|
||||
const uncompresssedLayerFile = "160d823fdc48e62f97ba62df31e55424f8f5eb6b679c865eec6e59adfe304710.tar"
|
||||
const uncompresssedLayerFile = "160d823fdc48e62f97ba62df31e55424f8f5eb6b679c865eec6e59adfe304710"
|
||||
|
||||
topDir, err := ioutil.TempDir("", "compression-top")
|
||||
c.Assert(err, check.IsNil)
|
||||
@@ -411,9 +414,7 @@ func (s *CopySuite) TestCopyCompression(c *check.C) {
|
||||
fis, err := dirf.Readdir(-1)
|
||||
c.Assert(err, check.IsNil)
|
||||
for _, fi := range fis {
|
||||
if strings.HasSuffix(fi.Name(), ".tar") {
|
||||
c.Assert(fi.Size() < 2048, check.Equals, true)
|
||||
}
|
||||
c.Assert(fi.Size() < 2048, check.Equals, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (s *SigningSuite) SetUpSuite(c *check.C) {
|
||||
c.Assert(err, check.IsNil)
|
||||
os.Setenv("GNUPGHOME", s.gpgHome)
|
||||
|
||||
runCommandWithInput(c, "Key-Type: RSA\nName-Real: Testing user\n%commit\n", gpgBinary, "--homedir", s.gpgHome, "--batch", "--gen-key")
|
||||
runCommandWithInput(c, "Key-Type: RSA\nName-Real: Testing user\n%no-protection\n%commit\n", gpgBinary, "--homedir", s.gpgHome, "--batch", "--gen-key")
|
||||
|
||||
lines, err := exec.Command(gpgBinary, "--homedir", s.gpgHome, "--with-colons", "--no-permission-warning", "--fingerprint").Output()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
26
vendor.conf
26
vendor.conf
@@ -1,5 +1,7 @@
|
||||
github.com/urfave/cli v1.17.0
|
||||
github.com/containers/image master
|
||||
github.com/kr/pretty v0.1.0
|
||||
github.com/kr/text v0.1.0
|
||||
github.com/containers/image skopeo-0.1.32-rhel
|
||||
github.com/opencontainers/go-digest master
|
||||
gopkg.in/cheggaaa/pb.v1 ad4efe000aa550bb54918c06ebbadc0ff17687b9 https://github.com/cheggaaa/pb
|
||||
github.com/containers/storage master
|
||||
@@ -10,9 +12,11 @@ github.com/davecgh/go-spew master
|
||||
github.com/pmezard/go-difflib master
|
||||
github.com/pkg/errors master
|
||||
golang.org/x/crypto master
|
||||
github.com/ulikunitz/xz v0.5.4
|
||||
# docker deps from https://github.com/docker/docker/blob/v1.11.2/hack/vendor.sh
|
||||
github.com/docker/docker 30eb4d8cdc422b023d5f11f29a82ecb73554183b
|
||||
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
|
||||
github.com/docker/docker da99009bbb1165d1ac5688b5c81d2f589d418341
|
||||
github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6
|
||||
github.com/containerd/continuity d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371
|
||||
github.com/vbatts/tar-split v0.10.2
|
||||
github.com/gorilla/context 14f550f51a
|
||||
github.com/gorilla/mux e444e69cbd
|
||||
@@ -22,10 +26,20 @@ github.com/gogo/protobuf fcdc5011193ff531a548e9b0301828d5a5b97fd8
|
||||
# end docker deps
|
||||
golang.org/x/text master
|
||||
github.com/docker/distribution master
|
||||
# docker/distributions dependencies
|
||||
github.com/docker/go-metrics 399ea8c73916000c64c2c76e8da00ca82f8387ab
|
||||
github.com/prometheus/client_golang c332b6f63c0658a65eca15c0e5247ded801cf564
|
||||
github.com/prometheus/client_model 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
|
||||
github.com/prometheus/common 89604d197083d4781071d3c65855d24ecfb0a563
|
||||
github.com/prometheus/procfs cb4147076ac75738c9a7d279075a253c0cc5acbd
|
||||
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
|
||||
github.com/matttproud/golang_protobuf_extensions c12348ce28de40eed0136aa2b644d0ee0650e56c
|
||||
github.com/golang/protobuf 8d92cf5fc15a4382f8964b08e1f42a75c0591aa3
|
||||
# end of docker/distribution dependencies
|
||||
github.com/docker/libtrust master
|
||||
github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1
|
||||
github.com/opencontainers/runc master
|
||||
github.com/opencontainers/image-spec v1.0.0
|
||||
github.com/opencontainers/image-spec unique-ids https://github.com/mtrmac/image-spec
|
||||
# -- start OCI image validation requirements.
|
||||
github.com/opencontainers/runtime-spec v1.0.0
|
||||
github.com/opencontainers/image-tools 6d941547fa1df31900990b3fb47ec2468c9c6469
|
||||
@@ -35,9 +49,8 @@ github.com/xeipuuv/gojsonpointer master
|
||||
go4.org master https://github.com/camlistore/go4
|
||||
github.com/ostreedev/ostree-go aeb02c6b6aa2889db3ef62f7855650755befd460
|
||||
# -- end OCI image validation requirements
|
||||
github.com/mtrmac/gpgme master
|
||||
github.com/mtrmac/gpgme v0.1.2
|
||||
# openshift/origin' k8s dependencies as of OpenShift v1.1.5
|
||||
github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed
|
||||
k8s.io/client-go master
|
||||
github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
||||
gopkg.in/yaml.v2 d466437aa4adc35830964cffc5b5f262c63ddcb4
|
||||
@@ -50,3 +63,4 @@ golang.org/x/sys master
|
||||
github.com/tchap/go-patricia v2.2.6
|
||||
github.com/BurntSushi/toml master
|
||||
github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac
|
||||
github.com/syndtr/gocapability master
|
||||
|
||||
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
31
vendor/github.com/beorn7/perks/README.md
generated
vendored
Normal file
31
vendor/github.com/beorn7/perks/README.md
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Perks for Go (golang.org)
|
||||
|
||||
Perks contains the Go package quantile that computes approximate quantiles over
|
||||
an unbounded data stream within low memory and CPU bounds.
|
||||
|
||||
For more information and examples, see:
|
||||
http://godoc.org/github.com/bmizerany/perks
|
||||
|
||||
A very special thank you and shout out to Graham Cormode (Rutgers University),
|
||||
Flip Korn (AT&T Labs–Research), S. Muthukrishnan (Rutgers University), and
|
||||
Divesh Srivastava (AT&T Labs–Research) for their research and publication of
|
||||
[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf)
|
||||
|
||||
Thank you, also:
|
||||
* Armon Dadgar (@armon)
|
||||
* Andrew Gerrand (@nf)
|
||||
* Brad Fitzpatrick (@bradfitz)
|
||||
* Keith Rarick (@kr)
|
||||
|
||||
FAQ:
|
||||
|
||||
Q: Why not move the quantile package into the project root?
|
||||
A: I want to add more packages to perks later.
|
||||
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
292
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
292
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targets map[float64]float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for quantile, epsilon := range targets {
|
||||
if quantile*s.n <= r {
|
||||
f = (2 * epsilon * r) / quantile
|
||||
} else {
|
||||
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(math.Ceil(float64(l) * q))
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
||||
202
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
202
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
|
||||
74
vendor/github.com/containerd/continuity/README.md
generated
vendored
Normal file
74
vendor/github.com/containerd/continuity/README.md
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# continuity
|
||||
|
||||
[](https://godoc.org/github.com/containerd/continuity)
|
||||
[](https://travis-ci.org/containerd/continuity)
|
||||
|
||||
A transport-agnostic, filesystem metadata manifest system
|
||||
|
||||
This project is a staging area for experiments in providing transport agnostic
|
||||
metadata storage.
|
||||
|
||||
Please see https://github.com/opencontainers/specs/issues/11 for more details.
|
||||
|
||||
## Manifest Format
|
||||
|
||||
A continuity manifest encodes filesystem metadata in Protocol Buffers.
|
||||
Please refer to [proto/manifest.proto](proto/manifest.proto).
|
||||
|
||||
## Usage
|
||||
|
||||
Build:
|
||||
|
||||
```console
|
||||
$ make
|
||||
```
|
||||
|
||||
Create a manifest (of this repo itself):
|
||||
|
||||
```console
|
||||
$ ./bin/continuity build . > /tmp/a.pb
|
||||
```
|
||||
|
||||
Dump a manifest:
|
||||
|
||||
```console
|
||||
$ ./bin/continuity ls /tmp/a.pb
|
||||
...
|
||||
-rw-rw-r-- 270 B /.gitignore
|
||||
-rw-rw-r-- 88 B /.mailmap
|
||||
-rw-rw-r-- 187 B /.travis.yml
|
||||
-rw-rw-r-- 359 B /AUTHORS
|
||||
-rw-rw-r-- 11 kB /LICENSE
|
||||
-rw-rw-r-- 1.5 kB /Makefile
|
||||
...
|
||||
-rw-rw-r-- 986 B /testutil_test.go
|
||||
drwxrwxr-x 0 B /version
|
||||
-rw-rw-r-- 478 B /version/version.go
|
||||
```
|
||||
|
||||
Verify a manifest:
|
||||
|
||||
```console
|
||||
$ ./bin/continuity verify . /tmp/a.pb
|
||||
```
|
||||
|
||||
Break the directory and restore using the manifest:
|
||||
```console
|
||||
$ chmod 777 Makefile
|
||||
$ ./bin/continuity verify . /tmp/a.pb
|
||||
2017/06/23 08:00:34 error verifying manifest: resource "/Makefile" has incorrect mode: -rwxrwxrwx != -rw-rw-r--
|
||||
$ ./bin/continuity apply . /tmp/a.pb
|
||||
$ stat -c %a Makefile
|
||||
664
|
||||
$ ./bin/continuity verify . /tmp/a.pb
|
||||
```
|
||||
|
||||
|
||||
## Contribution Guide
|
||||
### Building Proto Package
|
||||
|
||||
If you change the proto file you will need to rebuild the generated Go with `go generate`.
|
||||
|
||||
```console
|
||||
$ go generate ./proto
|
||||
```
|
||||
85
vendor/github.com/containerd/continuity/pathdriver/path_driver.go
generated
vendored
Normal file
85
vendor/github.com/containerd/continuity/pathdriver/path_driver.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package pathdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// PathDriver provides all of the path manipulation functions in a common
|
||||
// interface. The context should call these and never use the `filepath`
|
||||
// package or any other package to manipulate paths.
|
||||
type PathDriver interface {
|
||||
Join(paths ...string) string
|
||||
IsAbs(path string) bool
|
||||
Rel(base, target string) (string, error)
|
||||
Base(path string) string
|
||||
Dir(path string) string
|
||||
Clean(path string) string
|
||||
Split(path string) (dir, file string)
|
||||
Separator() byte
|
||||
Abs(path string) (string, error)
|
||||
Walk(string, filepath.WalkFunc) error
|
||||
FromSlash(path string) string
|
||||
ToSlash(path string) string
|
||||
Match(pattern, name string) (matched bool, err error)
|
||||
}
|
||||
|
||||
// pathDriver is a simple default implementation calls the filepath package.
|
||||
type pathDriver struct{}
|
||||
|
||||
// LocalPathDriver is the exported pathDriver struct for convenience.
|
||||
var LocalPathDriver PathDriver = &pathDriver{}
|
||||
|
||||
func (*pathDriver) Join(paths ...string) string {
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
|
||||
func (*pathDriver) IsAbs(path string) bool {
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Rel(base, target string) (string, error) {
|
||||
return filepath.Rel(base, target)
|
||||
}
|
||||
|
||||
func (*pathDriver) Base(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Dir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Clean(path string) string {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Split(path string) (dir, file string) {
|
||||
return filepath.Split(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Separator() byte {
|
||||
return filepath.Separator
|
||||
}
|
||||
|
||||
func (*pathDriver) Abs(path string) (string, error) {
|
||||
return filepath.Abs(path)
|
||||
}
|
||||
|
||||
// Note that filepath.Walk calls os.Stat, so if the context wants to
|
||||
// to call Driver.Stat() for Walk, they need to create a new struct that
|
||||
// overrides this method.
|
||||
func (*pathDriver) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
return filepath.Walk(root, walkFn)
|
||||
}
|
||||
|
||||
func (*pathDriver) FromSlash(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) ToSlash(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Match(pattern, name string) (bool, error) {
|
||||
return filepath.Match(pattern, name)
|
||||
}
|
||||
13
vendor/github.com/containerd/continuity/vendor.conf
generated
vendored
Normal file
13
vendor/github.com/containerd/continuity/vendor.conf
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
bazil.org/fuse 371fbbdaa8987b715bdd21d6adc4c9b20155f748
|
||||
github.com/dustin/go-humanize bb3d318650d48840a39aa21a027c6630e198e626
|
||||
github.com/golang/protobuf 1e59b77b52bf8e4b449a57e6f79f21226d571845
|
||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
github.com/opencontainers/go-digest 279bed98673dd5bef374d3b6e4b09e2af76183bf
|
||||
github.com/pkg/errors f15c970de5b76fac0b59abb32d62c17cc7bed265
|
||||
github.com/sirupsen/logrus 89742aefa4b206dcf400792f3bd35b542998eb3b
|
||||
github.com/spf13/cobra 2da4a54c5ceefcee7ca5dd0eea1e18a3b6366489
|
||||
github.com/spf13/pflag 4c012f6dcd9546820e378d0bdda4d8fc772cdfea
|
||||
golang.org/x/crypto 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94
|
||||
golang.org/x/net a337091b0525af65de94df2eb7e98bd9962dcbe2
|
||||
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
|
||||
golang.org/x/sys 665f6529cca930e27b831a0d1dafffbe1c172924
|
||||
14
vendor/github.com/containers/image/README.md
generated
vendored
14
vendor/github.com/containers/image/README.md
generated
vendored
@@ -25,7 +25,7 @@ them as necessary, and to sign and verify images.
|
||||
The containers/image project is only a library with no user interface;
|
||||
you can either incorporate it into your Go programs, or use the `skopeo` tool:
|
||||
|
||||
The [skopeo](https://github.com/projectatomic/skopeo) tool uses the
|
||||
The [skopeo](https://github.com/containers/skopeo) tool uses the
|
||||
containers/image library and takes advantage of many of its features,
|
||||
e.g. `skopeo copy` exposes the `containers/image/copy.Image` functionality.
|
||||
|
||||
@@ -42,7 +42,7 @@ What this project tests against dependencies-wise is located
|
||||
## Building
|
||||
|
||||
If you want to see what the library can do, or an example of how it is called,
|
||||
consider starting with the [skopeo](https://github.com/projectatomic/skopeo) tool
|
||||
consider starting with the [skopeo](https://github.com/containers/skopeo) tool
|
||||
instead.
|
||||
|
||||
To integrate this library into your project, put it into `$GOPATH` or use
|
||||
@@ -53,7 +53,7 @@ are also available
|
||||
|
||||
This library, by default, also depends on the GpgME and libostree C libraries. Either install them:
|
||||
```sh
|
||||
Fedora$ dnf install gpgme-devel libassuan-devel libostree-devel
|
||||
Fedora$ dnf install gpgme-devel libassuan-devel ostree-devel
|
||||
macOS$ brew install gpgme
|
||||
```
|
||||
or use the build tags described below to avoid the dependencies (e.g. using `go build -tags …`)
|
||||
@@ -65,13 +65,17 @@ the primary downside is that creating new signatures with the Golang-only implem
|
||||
- `containers_image_ostree_stub`: Instead of importing `ostree:` transport in `github.com/containers/image/transports/alltransports`, use a stub which reports that the transport is not supported. This allows building the library without requiring the `libostree` development libraries. The `github.com/containers/image/ostree` package is completely disabled
|
||||
and impossible to import when this build tag is in use.
|
||||
|
||||
## Contributing
|
||||
## [Contributing](CONTRIBUTING.md)**
|
||||
|
||||
Information about contributing to this project.
|
||||
|
||||
When developing this library, please use `make` (or `make … BUILDTAGS=…`) to take advantage of the tests and validation.
|
||||
|
||||
## License
|
||||
|
||||
ASL 2.0
|
||||
Apache License 2.0
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
198
vendor/github.com/containers/image/copy/copy.go
generated
vendored
198
vendor/github.com/containers/image/copy/copy.go
generated
vendored
@@ -71,7 +71,6 @@ func (d *digestingReader) Read(p []byte) (int, error) {
|
||||
// copier allows us to keep track of diffID values for blobs, and other
|
||||
// data shared across one or more images in a possible manifest list.
|
||||
type copier struct {
|
||||
copiedBlobs map[digest.Digest]digest.Digest
|
||||
cachedDiffIDs map[digest.Digest]digest.Digest
|
||||
dest types.ImageDestination
|
||||
rawSource types.ImageSource
|
||||
@@ -103,8 +102,9 @@ type Options struct {
|
||||
}
|
||||
|
||||
// Image copies image from srcRef to destRef, using policyContext to validate
|
||||
// source image admissibility.
|
||||
func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (retErr error) {
|
||||
// source image admissibility. It returns the manifest which was written to
|
||||
// the new copy of the image.
|
||||
func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (manifest []byte, retErr error) {
|
||||
// NOTE this function uses an output parameter for the error return value.
|
||||
// Setting this and returning is the ideal way to return an error.
|
||||
//
|
||||
@@ -120,9 +120,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
reportWriter = options.ReportWriter
|
||||
}
|
||||
|
||||
dest, err := destRef.NewImageDestination(options.DestinationCtx)
|
||||
dest, err := destRef.NewImageDestination(ctx, options.DestinationCtx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error initializing destination %s", transports.ImageName(destRef))
|
||||
return nil, errors.Wrapf(err, "Error initializing destination %s", transports.ImageName(destRef))
|
||||
}
|
||||
defer func() {
|
||||
if err := dest.Close(); err != nil {
|
||||
@@ -130,9 +130,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
}
|
||||
}()
|
||||
|
||||
rawSource, err := srcRef.NewImageSource(options.SourceCtx)
|
||||
rawSource, err := srcRef.NewImageSource(ctx, options.SourceCtx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error initializing source %s", transports.ImageName(srcRef))
|
||||
return nil, errors.Wrapf(err, "Error initializing source %s", transports.ImageName(srcRef))
|
||||
}
|
||||
defer func() {
|
||||
if err := rawSource.Close(); err != nil {
|
||||
@@ -141,7 +141,6 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
}()
|
||||
|
||||
c := &copier{
|
||||
copiedBlobs: make(map[digest.Digest]digest.Digest),
|
||||
cachedDiffIDs: make(map[digest.Digest]digest.Digest),
|
||||
dest: dest,
|
||||
rawSource: rawSource,
|
||||
@@ -151,65 +150,65 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
}
|
||||
|
||||
unparsedToplevel := image.UnparsedInstance(rawSource, nil)
|
||||
multiImage, err := isMultiImage(unparsedToplevel)
|
||||
multiImage, err := isMultiImage(ctx, unparsedToplevel)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(srcRef))
|
||||
return nil, errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(srcRef))
|
||||
}
|
||||
|
||||
if !multiImage {
|
||||
// The simple case: Just copy a single image.
|
||||
if err := c.copyOneImage(policyContext, options, unparsedToplevel); err != nil {
|
||||
return err
|
||||
if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// This is a manifest list. Choose a single image and copy it.
|
||||
// FIXME: Copy to destinations which support manifest lists, one image at a time.
|
||||
instanceDigest, err := image.ChooseManifestInstanceFromManifestList(options.SourceCtx, unparsedToplevel)
|
||||
instanceDigest, err := image.ChooseManifestInstanceFromManifestList(ctx, options.SourceCtx, unparsedToplevel)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error choosing an image from manifest list %s", transports.ImageName(srcRef))
|
||||
return nil, errors.Wrapf(err, "Error choosing an image from manifest list %s", transports.ImageName(srcRef))
|
||||
}
|
||||
logrus.Debugf("Source is a manifest list; copying (only) instance %s", instanceDigest)
|
||||
unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest)
|
||||
|
||||
if err := c.copyOneImage(policyContext, options, unparsedInstance); err != nil {
|
||||
return err
|
||||
if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedInstance); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.dest.Commit(); err != nil {
|
||||
return errors.Wrap(err, "Error committing the finished image")
|
||||
if err := c.dest.Commit(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "Error committing the finished image")
|
||||
}
|
||||
|
||||
return nil
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
// Image copies a single (on-manifest-list) image unparsedImage, using policyContext to validate
|
||||
// source image admissibility.
|
||||
func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (retErr error) {
|
||||
func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifest []byte, retErr error) {
|
||||
// The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list.
|
||||
// Make sure we fail cleanly in such cases.
|
||||
multiImage, err := isMultiImage(unparsedImage)
|
||||
multiImage, err := isMultiImage(ctx, unparsedImage)
|
||||
if err != nil {
|
||||
// FIXME FIXME: How to name a reference for the sub-image?
|
||||
return errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference()))
|
||||
return nil, errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference()))
|
||||
}
|
||||
if multiImage {
|
||||
return fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
|
||||
return nil, fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image")
|
||||
}
|
||||
|
||||
// Please keep this policy check BEFORE reading any other information about the image.
|
||||
// (the multiImage check above only matches the MIME type, which we have received anyway.
|
||||
// Actual parsing of anything should be deferred.)
|
||||
if allowed, err := policyContext.IsRunningImageAllowed(unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
|
||||
return errors.Wrap(err, "Source image rejected")
|
||||
if allowed, err := policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so.
|
||||
return nil, errors.Wrap(err, "Source image rejected")
|
||||
}
|
||||
src, err := image.FromUnparsedImage(options.SourceCtx, unparsedImage)
|
||||
src, err := image.FromUnparsedImage(ctx, options.SourceCtx, unparsedImage)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
|
||||
return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
|
||||
}
|
||||
|
||||
if err := checkImageDestinationForCurrentRuntimeOS(options.DestinationCtx, src, c.dest); err != nil {
|
||||
return err
|
||||
if err := checkImageDestinationForCurrentRuntimeOS(ctx, options.DestinationCtx, src, c.dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sigs [][]byte
|
||||
@@ -217,16 +216,16 @@ func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *O
|
||||
sigs = [][]byte{}
|
||||
} else {
|
||||
c.Printf("Getting image source signatures\n")
|
||||
s, err := src.Signatures(context.TODO())
|
||||
s, err := src.Signatures(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading signatures")
|
||||
return nil, errors.Wrap(err, "Error reading signatures")
|
||||
}
|
||||
sigs = s
|
||||
}
|
||||
if len(sigs) != 0 {
|
||||
c.Printf("Checking if image destination supports signatures\n")
|
||||
if err := c.dest.SupportsSignatures(); err != nil {
|
||||
return errors.Wrap(err, "Can not copy signatures")
|
||||
if err := c.dest.SupportsSignatures(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "Can not copy signatures")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,28 +238,28 @@ func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *O
|
||||
}
|
||||
|
||||
if err := ic.updateEmbeddedDockerReference(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We compute preferredManifestMIMEType only to show it in error messages.
|
||||
// Without having to add this context in an error message, we would be happy enough to know only that no conversion is needed.
|
||||
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := ic.determineManifestConversion(c.dest.SupportedManifestMIMETypes(), options.ForceManifestMIMEType)
|
||||
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := ic.determineManifestConversion(ctx, c.dest.SupportedManifestMIMETypes(), options.ForceManifestMIMEType)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If src.UpdatedImageNeedsLayerDiffIDs(ic.manifestUpdates) will be true, it needs to be true by the time we get here.
|
||||
ic.diffIDsAreNeeded = src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates)
|
||||
|
||||
if err := ic.copyLayers(); err != nil {
|
||||
return err
|
||||
if err := ic.copyLayers(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With docker/distribution registries we do not know whether the registry accepts schema2 or schema1 only;
|
||||
// and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support
|
||||
// without actually trying to upload something and getting a types.ManifestTypeRejectedError.
|
||||
// So, try the preferred manifest MIME type. If the process succeeds, fine…
|
||||
manifest, err := ic.copyUpdatedConfigAndManifest()
|
||||
manifest, err = ic.copyUpdatedConfigAndManifest(ctx)
|
||||
if err != nil {
|
||||
logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err)
|
||||
// … if it fails, _and_ the failure is because the manifest is rejected, we may have other options.
|
||||
@@ -268,14 +267,14 @@ func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *O
|
||||
// We don’t have other options.
|
||||
// In principle the code below would handle this as well, but the resulting error message is fairly ugly.
|
||||
// Don’t bother the user with MIME types if we have no choice.
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType.
|
||||
// So if we are here, we will definitely be trying to convert the manifest.
|
||||
// With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason,
|
||||
// so let’s bail out early and with a better error message.
|
||||
if !ic.canModifyManifest {
|
||||
return errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
|
||||
return nil, errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
|
||||
}
|
||||
|
||||
// errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil.
|
||||
@@ -283,7 +282,7 @@ func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *O
|
||||
for _, manifestMIMEType := range otherManifestMIMETypeCandidates {
|
||||
logrus.Debugf("Trying to use manifest type %s…", manifestMIMEType)
|
||||
ic.manifestUpdates.ManifestMIMEType = manifestMIMEType
|
||||
attemptedManifest, err := ic.copyUpdatedConfigAndManifest()
|
||||
attemptedManifest, err := ic.copyUpdatedConfigAndManifest(ctx)
|
||||
if err != nil {
|
||||
logrus.Debugf("Upload of manifest type %s failed: %v", manifestMIMEType, err)
|
||||
errs = append(errs, fmt.Sprintf("%s(%v)", manifestMIMEType, err))
|
||||
@@ -296,24 +295,24 @@ func (c *copier) copyOneImage(policyContext *signature.PolicyContext, options *O
|
||||
break
|
||||
}
|
||||
if errs != nil {
|
||||
return fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
|
||||
return nil, fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if options.SignBy != "" {
|
||||
newSig, err := c.createSignature(manifest, options.SignBy)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
sigs = append(sigs, newSig)
|
||||
}
|
||||
|
||||
c.Printf("Storing signatures\n")
|
||||
if err := c.dest.PutSignatures(sigs); err != nil {
|
||||
return errors.Wrap(err, "Error writing signatures")
|
||||
if err := c.dest.PutSignatures(ctx, sigs); err != nil {
|
||||
return nil, errors.Wrap(err, "Error writing signatures")
|
||||
}
|
||||
|
||||
return nil
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
// Printf writes a formatted string to c.reportWriter.
|
||||
@@ -325,13 +324,13 @@ func (c *copier) Printf(format string, a ...interface{}) {
|
||||
fmt.Fprintf(c.reportWriter, format, a...)
|
||||
}
|
||||
|
||||
func checkImageDestinationForCurrentRuntimeOS(ctx *types.SystemContext, src types.Image, dest types.ImageDestination) error {
|
||||
func checkImageDestinationForCurrentRuntimeOS(ctx context.Context, sys *types.SystemContext, src types.Image, dest types.ImageDestination) error {
|
||||
if dest.MustMatchRuntimeOS() {
|
||||
wantedOS := runtime.GOOS
|
||||
if ctx != nil && ctx.OSChoice != "" {
|
||||
wantedOS = ctx.OSChoice
|
||||
if sys != nil && sys.OSChoice != "" {
|
||||
wantedOS = sys.OSChoice
|
||||
}
|
||||
c, err := src.OCIConfig()
|
||||
c, err := src.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error parsing image configuration")
|
||||
}
|
||||
@@ -347,6 +346,9 @@ func checkImageDestinationForCurrentRuntimeOS(ctx *types.SystemContext, src type
|
||||
|
||||
// updateEmbeddedDockerReference handles the Docker reference embedded in Docker schema1 manifests.
|
||||
func (ic *imageCopier) updateEmbeddedDockerReference() error {
|
||||
if ic.c.dest.IgnoresEmbeddedDockerReference() {
|
||||
return nil // Destination would prefer us not to update the embedded reference.
|
||||
}
|
||||
destRef := ic.c.dest.Reference().DockerReference()
|
||||
if destRef == nil {
|
||||
return nil // Destination does not care about Docker references
|
||||
@@ -364,10 +366,22 @@ func (ic *imageCopier) updateEmbeddedDockerReference() error {
|
||||
}
|
||||
|
||||
// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest.
|
||||
func (ic *imageCopier) copyLayers() error {
|
||||
func (ic *imageCopier) copyLayers(ctx context.Context) error {
|
||||
srcInfos := ic.src.LayerInfos()
|
||||
destInfos := []types.BlobInfo{}
|
||||
diffIDs := []digest.Digest{}
|
||||
updatedSrcInfos, err := ic.src.LayerInfosForCopy(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcInfosUpdated := false
|
||||
if updatedSrcInfos != nil && !reflect.DeepEqual(srcInfos, updatedSrcInfos) {
|
||||
if !ic.canModifyManifest {
|
||||
return errors.Errorf("Internal error: copyLayers() needs to use an updated manifest but that was known to be forbidden")
|
||||
}
|
||||
srcInfos = updatedSrcInfos
|
||||
srcInfosUpdated = true
|
||||
}
|
||||
for _, srcLayer := range srcInfos {
|
||||
var (
|
||||
destInfo types.BlobInfo
|
||||
@@ -384,7 +398,7 @@ func (ic *imageCopier) copyLayers() error {
|
||||
destInfo = srcLayer
|
||||
ic.c.Printf("Skipping foreign layer %q copy to %s\n", destInfo.Digest, ic.c.dest.Reference().Transport().Name())
|
||||
} else {
|
||||
destInfo, diffID, err = ic.copyLayer(srcLayer)
|
||||
destInfo, diffID, err = ic.copyLayer(ctx, srcLayer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -396,7 +410,7 @@ func (ic *imageCopier) copyLayers() error {
|
||||
if ic.diffIDsAreNeeded {
|
||||
ic.manifestUpdates.InformationOnly.LayerDiffIDs = diffIDs
|
||||
}
|
||||
if layerDigestsDiffer(srcInfos, destInfos) {
|
||||
if srcInfosUpdated || layerDigestsDiffer(srcInfos, destInfos) {
|
||||
ic.manifestUpdates.LayerInfos = destInfos
|
||||
}
|
||||
return nil
|
||||
@@ -417,7 +431,7 @@ func layerDigestsDiffer(a, b []types.BlobInfo) bool {
|
||||
|
||||
// copyUpdatedConfigAndManifest updates the image per ic.manifestUpdates, if necessary,
|
||||
// stores the resulting config and manifest to the destination, and returns the stored manifest.
|
||||
func (ic *imageCopier) copyUpdatedConfigAndManifest() ([]byte, error) {
|
||||
func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context) ([]byte, error) {
|
||||
pendingImage := ic.src
|
||||
if !reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly}) {
|
||||
if !ic.canModifyManifest {
|
||||
@@ -432,38 +446,38 @@ func (ic *imageCopier) copyUpdatedConfigAndManifest() ([]byte, error) {
|
||||
// If handling such registries turns out to be necessary, we could compute ic.diffIDsAreNeeded based on the full list of manifest MIME type candidates.
|
||||
return nil, errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType)
|
||||
}
|
||||
pi, err := ic.src.UpdatedImage(*ic.manifestUpdates)
|
||||
pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating an updated image manifest")
|
||||
}
|
||||
pendingImage = pi
|
||||
}
|
||||
manifest, _, err := pendingImage.Manifest()
|
||||
manifest, _, err := pendingImage.Manifest(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading manifest")
|
||||
}
|
||||
|
||||
if err := ic.c.copyConfig(pendingImage); err != nil {
|
||||
if err := ic.c.copyConfig(ctx, pendingImage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ic.c.Printf("Writing manifest to image destination\n")
|
||||
if err := ic.c.dest.PutManifest(manifest); err != nil {
|
||||
if err := ic.c.dest.PutManifest(ctx, manifest); err != nil {
|
||||
return nil, errors.Wrap(err, "Error writing manifest")
|
||||
}
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
// copyConfig copies config.json, if any, from src to dest.
|
||||
func (c *copier) copyConfig(src types.Image) error {
|
||||
func (c *copier) copyConfig(ctx context.Context, src types.Image) error {
|
||||
srcInfo := src.ConfigInfo()
|
||||
if srcInfo.Digest != "" {
|
||||
c.Printf("Copying config %s\n", srcInfo.Digest)
|
||||
configBlob, err := src.ConfigBlob()
|
||||
configBlob, err := src.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error reading config blob %s", srcInfo.Digest)
|
||||
}
|
||||
destInfo, err := c.copyBlobFromStream(bytes.NewReader(configBlob), srcInfo, nil, false)
|
||||
destInfo, err := c.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -483,9 +497,9 @@ type diffIDResult struct {
|
||||
|
||||
// copyLayer copies a layer with srcInfo (with known Digest and possibly known Size) in src to dest, perhaps compressing it if canCompress,
|
||||
// and returns a complete blobInfo of the copied layer, and a value for LayerDiffIDs if diffIDIsNeeded
|
||||
func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest.Digest, error) {
|
||||
func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo) (types.BlobInfo, digest.Digest, error) {
|
||||
// Check if we already have a blob with this digest
|
||||
haveBlob, extantBlobSize, err := ic.c.dest.HasBlob(srcInfo)
|
||||
haveBlob, extantBlobSize, err := ic.c.dest.HasBlob(ctx, srcInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, "", errors.Wrapf(err, "Error checking for blob %s at destination", srcInfo.Digest)
|
||||
}
|
||||
@@ -499,7 +513,7 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest
|
||||
}
|
||||
srcInfo.Size = extantBlobSize
|
||||
// Tell the image destination that this blob's delta is being applied again. For some image destinations, this can be faster than using GetBlob/PutBlob
|
||||
blobinfo, err := ic.c.dest.ReapplyBlob(srcInfo)
|
||||
blobinfo, err := ic.c.dest.ReapplyBlob(ctx, srcInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, "", errors.Wrapf(err, "Error reapplying blob %s at destination", srcInfo.Digest)
|
||||
}
|
||||
@@ -509,34 +523,39 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest
|
||||
|
||||
// Fallback: copy the layer, computing the diffID if we need to do so
|
||||
ic.c.Printf("Copying blob %s\n", srcInfo.Digest)
|
||||
srcStream, srcBlobSize, err := ic.c.rawSource.GetBlob(srcInfo)
|
||||
srcStream, srcBlobSize, err := ic.c.rawSource.GetBlob(ctx, srcInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, "", errors.Wrapf(err, "Error reading blob %s", srcInfo.Digest)
|
||||
}
|
||||
defer srcStream.Close()
|
||||
|
||||
blobInfo, diffIDChan, err := ic.copyLayerFromStream(srcStream, types.BlobInfo{Digest: srcInfo.Digest, Size: srcBlobSize},
|
||||
blobInfo, diffIDChan, err := ic.copyLayerFromStream(ctx, srcStream, types.BlobInfo{Digest: srcInfo.Digest, Size: srcBlobSize},
|
||||
diffIDIsNeeded)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, "", err
|
||||
}
|
||||
var diffIDResult diffIDResult // = {digest:""}
|
||||
if diffIDIsNeeded {
|
||||
diffIDResult = <-diffIDChan
|
||||
if diffIDResult.err != nil {
|
||||
return types.BlobInfo{}, "", errors.Wrap(diffIDResult.err, "Error computing layer DiffID")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return types.BlobInfo{}, "", ctx.Err()
|
||||
case diffIDResult := <-diffIDChan:
|
||||
if diffIDResult.err != nil {
|
||||
return types.BlobInfo{}, "", errors.Wrap(diffIDResult.err, "Error computing layer DiffID")
|
||||
}
|
||||
logrus.Debugf("Computed DiffID %s for layer %s", diffIDResult.digest, srcInfo.Digest)
|
||||
ic.c.cachedDiffIDs[srcInfo.Digest] = diffIDResult.digest
|
||||
return blobInfo, diffIDResult.digest, nil
|
||||
}
|
||||
logrus.Debugf("Computed DiffID %s for layer %s", diffIDResult.digest, srcInfo.Digest)
|
||||
ic.c.cachedDiffIDs[srcInfo.Digest] = diffIDResult.digest
|
||||
} else {
|
||||
return blobInfo, ic.c.cachedDiffIDs[srcInfo.Digest], nil
|
||||
}
|
||||
return blobInfo, diffIDResult.digest, nil
|
||||
}
|
||||
|
||||
// copyLayerFromStream is an implementation detail of copyLayer; mostly providing a separate “defer” scope.
|
||||
// it copies a blob with srcInfo (with known Digest and possibly known Size) from srcStream to dest,
|
||||
// perhaps compressing the stream if canCompress,
|
||||
// and returns a complete blobInfo of the copied blob and perhaps a <-chan diffIDResult if diffIDIsNeeded, to be read by the caller.
|
||||
func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
func (ic *imageCopier) copyLayerFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
diffIDIsNeeded bool) (types.BlobInfo, <-chan diffIDResult, error) {
|
||||
var getDiffIDRecorder func(compression.DecompressorFunc) io.Writer // = nil
|
||||
var diffIDChan chan diffIDResult
|
||||
@@ -561,7 +580,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl
|
||||
return pipeWriter
|
||||
}
|
||||
}
|
||||
blobInfo, err := ic.c.copyBlobFromStream(srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest) // Sets err to nil on success
|
||||
blobInfo, err := ic.c.copyBlobFromStream(ctx, srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest, false) // Sets err to nil on success
|
||||
return blobInfo, diffIDChan, err
|
||||
// We need the defer … pipeWriter.CloseWithError() to happen HERE so that the caller can block on reading from diffIDChan
|
||||
}
|
||||
@@ -585,6 +604,7 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer s.Close()
|
||||
stream = s
|
||||
}
|
||||
|
||||
@@ -595,9 +615,9 @@ func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc)
|
||||
// perhaps sending a copy to an io.Writer if getOriginalLayerCopyWriter != nil,
|
||||
// perhaps compressing it if canCompress,
|
||||
// and returns a complete blobInfo of the copied blob.
|
||||
func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
getOriginalLayerCopyWriter func(decompressor compression.DecompressorFunc) io.Writer,
|
||||
canCompress bool) (types.BlobInfo, error) {
|
||||
canModifyBlob bool, isConfig bool) (types.BlobInfo, error) {
|
||||
// The copying happens through a pipeline of connected io.Readers.
|
||||
// === Input: srcStream
|
||||
|
||||
@@ -638,12 +658,9 @@ func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
originalLayerReader = destStream
|
||||
}
|
||||
|
||||
// === Compress the layer if it is uncompressed and compression is desired
|
||||
// === Deal with layer compression/decompression if necessary
|
||||
var inputInfo types.BlobInfo
|
||||
if !canCompress || isCompressed || !c.dest.ShouldCompressLayers() {
|
||||
logrus.Debugf("Using original blob without modification")
|
||||
inputInfo = srcInfo
|
||||
} else {
|
||||
if canModifyBlob && c.dest.DesiredLayerCompression() == types.Compress && !isCompressed {
|
||||
logrus.Debugf("Compressing blob on the fly")
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
defer pipeReader.Close()
|
||||
@@ -655,6 +672,19 @@ func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
destStream = pipeReader
|
||||
inputInfo.Digest = ""
|
||||
inputInfo.Size = -1
|
||||
} else if canModifyBlob && c.dest.DesiredLayerCompression() == types.Decompress && isCompressed {
|
||||
logrus.Debugf("Blob will be decompressed")
|
||||
s, err := decompressor(destStream)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer s.Close()
|
||||
destStream = s
|
||||
inputInfo.Digest = ""
|
||||
inputInfo.Size = -1
|
||||
} else {
|
||||
logrus.Debugf("Using original blob without modification")
|
||||
inputInfo = srcInfo
|
||||
}
|
||||
|
||||
// === Report progress using the c.progress channel, if required.
|
||||
@@ -669,7 +699,7 @@ func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
|
||||
}
|
||||
|
||||
// === Finally, send the layer stream to dest.
|
||||
uploadedInfo, err := c.dest.PutBlob(destStream, inputInfo)
|
||||
uploadedInfo, err := c.dest.PutBlob(ctx, destStream, inputInfo, isConfig)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "Error writing blob")
|
||||
}
|
||||
|
||||
14
vendor/github.com/containers/image/copy/manifest.go
generated
vendored
14
vendor/github.com/containers/image/copy/manifest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package copy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
@@ -41,11 +42,16 @@ func (os *orderedSet) append(s string) {
|
||||
// Note that the conversion will only happen later, through ic.src.UpdatedImage
|
||||
// Returns the preferred manifest MIME type (whether we are converting to it or using it unmodified),
|
||||
// and a list of other possible alternatives, in order.
|
||||
func (ic *imageCopier) determineManifestConversion(destSupportedManifestMIMETypes []string, forceManifestMIMEType string) (string, []string, error) {
|
||||
_, srcType, err := ic.src.Manifest()
|
||||
func (ic *imageCopier) determineManifestConversion(ctx context.Context, destSupportedManifestMIMETypes []string, forceManifestMIMEType string) (string, []string, error) {
|
||||
_, srcType, err := ic.src.Manifest(ctx)
|
||||
if err != nil { // This should have been cached?!
|
||||
return "", nil, errors.Wrap(err, "Error reading manifest")
|
||||
}
|
||||
normalizedSrcType := manifest.NormalizedMIMEType(srcType)
|
||||
if srcType != normalizedSrcType {
|
||||
logrus.Debugf("Source manifest MIME type %s, treating it as %s", srcType, normalizedSrcType)
|
||||
srcType = normalizedSrcType
|
||||
}
|
||||
|
||||
if forceManifestMIMEType != "" {
|
||||
destSupportedManifestMIMETypes = []string{forceManifestMIMEType}
|
||||
@@ -106,8 +112,8 @@ func (ic *imageCopier) determineManifestConversion(destSupportedManifestMIMEType
|
||||
}
|
||||
|
||||
// isMultiImage returns true if img is a list of images
|
||||
func isMultiImage(img types.UnparsedImage) (bool, error) {
|
||||
_, mt, err := img.Manifest()
|
||||
func isMultiImage(ctx context.Context, img types.UnparsedImage) (bool, error) {
|
||||
_, mt, err := img.Manifest(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
35
vendor/github.com/containers/image/directory/directory_dest.go
generated
vendored
35
vendor/github.com/containers/image/directory/directory_dest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const version = "Directory Transport Version: 1.0\n"
|
||||
const version = "Directory Transport Version: 1.1\n"
|
||||
|
||||
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
|
||||
// using the 'dir' transport
|
||||
@@ -70,7 +71,7 @@ func newImageDestination(ref dirReference, compress bool) (types.ImageDestinatio
|
||||
}
|
||||
}
|
||||
// create version file
|
||||
err = ioutil.WriteFile(d.ref.versionPath(), []byte(version), 0755)
|
||||
err = ioutil.WriteFile(d.ref.versionPath(), []byte(version), 0644)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating version file %q", d.ref.versionPath())
|
||||
}
|
||||
@@ -94,13 +95,15 @@ func (d *dirImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *dirImageDestination) SupportsSignatures() error {
|
||||
func (d *dirImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *dirImageDestination) ShouldCompressLayers() bool {
|
||||
return d.compress
|
||||
func (d *dirImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
if d.compress {
|
||||
return types.Compress
|
||||
}
|
||||
return types.PreserveOriginal
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -114,13 +117,20 @@ func (d *dirImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *dirImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, DockerReference() returns nil.
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *dirImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *dirImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
blobFile, err := ioutil.TempFile(d.ref.path, "dir-put-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -136,6 +146,7 @@ func (d *dirImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
size, err := io.Copy(blobFile, tee)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -162,7 +173,7 @@ func (d *dirImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *dirImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
func (d *dirImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
@@ -177,7 +188,7 @@ func (d *dirImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error)
|
||||
return true, finfo.Size(), nil
|
||||
}
|
||||
|
||||
func (d *dirImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *dirImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -185,11 +196,11 @@ func (d *dirImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo,
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *dirImageDestination) PutManifest(manifest []byte) error {
|
||||
func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte) error {
|
||||
return ioutil.WriteFile(d.ref.manifestPath(), manifest, 0644)
|
||||
}
|
||||
|
||||
func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
for i, sig := range signatures {
|
||||
if err := ioutil.WriteFile(d.ref.signaturePath(i), sig, 0644); err != nil {
|
||||
return err
|
||||
@@ -202,7 +213,7 @@ func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *dirImageDestination) Commit() error {
|
||||
func (d *dirImageDestination) Commit(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
13
vendor/github.com/containers/image/directory/directory_src.go
generated
vendored
13
vendor/github.com/containers/image/directory/directory_src.go
generated
vendored
@@ -37,7 +37,7 @@ func (s *dirImageSource) Close() error {
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *dirImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
func (s *dirImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
|
||||
}
|
||||
@@ -49,14 +49,14 @@ func (s *dirImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, str
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
func (s *dirImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
r, err := os.Open(s.ref.layerPath(info.Digest))
|
||||
if err != nil {
|
||||
return nil, 0, nil
|
||||
return nil, -1, err
|
||||
}
|
||||
fi, err := r.Stat()
|
||||
if err != nil {
|
||||
return nil, 0, nil
|
||||
return nil, -1, err
|
||||
}
|
||||
return r, fi.Size(), nil
|
||||
}
|
||||
@@ -82,3 +82,8 @@ func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *dige
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when copying, in preference to values in the manifest, if specified.
|
||||
func (s *dirImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
20
vendor/github.com/containers/image/directory/directory_transport.go
generated
vendored
20
vendor/github.com/containers/image/directory/directory_transport.go
generated
vendored
@@ -1,18 +1,18 @@
|
||||
package directory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -139,29 +139,29 @@ func (ref dirReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref dirReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
func (ref dirReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src := newImageSource(ref)
|
||||
return image.FromSource(ctx, src)
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref dirReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
func (ref dirReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ref), nil
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref dirReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
func (ref dirReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
compress := false
|
||||
if ctx != nil {
|
||||
compress = ctx.DirForceCompress
|
||||
if sys != nil {
|
||||
compress = sys.DirForceCompress
|
||||
}
|
||||
return newImageDestination(ref, compress)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref dirReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref dirReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for dir: images")
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func (ref dirReference) manifestPath() string {
|
||||
// layerPath returns a path for a layer tarball within a directory using our conventions.
|
||||
func (ref dirReference) layerPath(digest digest.Digest) string {
|
||||
// FIXME: Should we keep the digest identification?
|
||||
return filepath.Join(ref.path, digest.Hex()+".tar")
|
||||
return filepath.Join(ref.path, digest.Hex())
|
||||
}
|
||||
|
||||
// signaturePath returns a path for a signature within a directory using our conventions.
|
||||
|
||||
22
vendor/github.com/containers/image/docker/archive/dest.go
generated
vendored
22
vendor/github.com/containers/image/docker/archive/dest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@@ -15,11 +16,7 @@ type archiveImageDestination struct {
|
||||
writer io.Closer
|
||||
}
|
||||
|
||||
func newImageDestination(ctx *types.SystemContext, ref archiveReference) (types.ImageDestination, error) {
|
||||
if ref.destinationRef == nil {
|
||||
return nil, errors.Errorf("docker-archive: destination reference not supplied (must be of form <path>:<reference:tag>)")
|
||||
}
|
||||
|
||||
func newImageDestination(sys *types.SystemContext, ref archiveReference) (types.ImageDestination, error) {
|
||||
// ref.path can be either a pipe or a regular file
|
||||
// in the case of a pipe, we require that we can open it for write
|
||||
// in the case of a regular file, we don't want to overwrite any pre-existing file
|
||||
@@ -39,13 +36,22 @@ func newImageDestination(ctx *types.SystemContext, ref archiveReference) (types.
|
||||
return nil, errors.New("docker-archive doesn't support modifying existing images")
|
||||
}
|
||||
|
||||
tarDest := tarfile.NewDestination(fh, ref.destinationRef)
|
||||
if sys != nil && sys.DockerArchiveAdditionalTags != nil {
|
||||
tarDest.AddRepoTags(sys.DockerArchiveAdditionalTags)
|
||||
}
|
||||
return &archiveImageDestination{
|
||||
Destination: tarfile.NewDestination(fh, ref.destinationRef),
|
||||
Destination: tarDest,
|
||||
ref: ref,
|
||||
writer: fh,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DesiredLayerCompression indicates if layers must be compressed, decompressed or preserved
|
||||
func (d *archiveImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.Decompress
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects.
|
||||
func (d *archiveImageDestination) Reference() types.ImageReference {
|
||||
@@ -61,6 +67,6 @@ func (d *archiveImageDestination) Close() error {
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *archiveImageDestination) Commit() error {
|
||||
return d.Destination.Commit()
|
||||
func (d *archiveImageDestination) Commit(ctx context.Context) error {
|
||||
return d.Destination.Commit(ctx)
|
||||
}
|
||||
|
||||
16
vendor/github.com/containers/image/docker/archive/src.go
generated
vendored
16
vendor/github.com/containers/image/docker/archive/src.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/containers/image/docker/tarfile"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -13,15 +14,18 @@ type archiveImageSource struct {
|
||||
|
||||
// newImageSource returns a types.ImageSource for the specified image reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx *types.SystemContext, ref archiveReference) types.ImageSource {
|
||||
func newImageSource(ctx context.Context, ref archiveReference) (types.ImageSource, error) {
|
||||
if ref.destinationRef != nil {
|
||||
logrus.Warnf("docker-archive: references are not supported for sources (ignoring)")
|
||||
}
|
||||
src := tarfile.NewSource(ref.path)
|
||||
src, err := tarfile.NewSourceFromFile(ref.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &archiveImageSource{
|
||||
Source: src,
|
||||
ref: ref,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source, _as specified by the user_
|
||||
@@ -30,7 +34,7 @@ func (s *archiveImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *archiveImageSource) Close() error {
|
||||
return nil
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *archiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
24
vendor/github.com/containers/image/docker/archive/transport.go
generated
vendored
24
vendor/github.com/containers/image/docker/archive/transport.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -40,7 +41,9 @@ func (t archiveTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
|
||||
// archiveReference is an ImageReference for Docker images.
|
||||
type archiveReference struct {
|
||||
destinationRef reference.NamedTagged // only used for destinations
|
||||
// only used for destinations
|
||||
// archiveReference.destinationRef is optional and can be nil for destinations as well.
|
||||
destinationRef reference.NamedTagged
|
||||
path string
|
||||
}
|
||||
|
||||
@@ -130,25 +133,28 @@ func (ref archiveReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref archiveReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src := newImageSource(ctx, ref)
|
||||
return ctrImage.FromSource(ctx, src)
|
||||
func (ref archiveReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctrImage.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref archiveReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref), nil
|
||||
func (ref archiveReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref archiveReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref archiveReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref archiveReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref archiveReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
// Not really supported, for safety reasons.
|
||||
return errors.New("Deleting images not implemented for docker-archive: images")
|
||||
}
|
||||
|
||||
46
vendor/github.com/containers/image/docker/daemon/client.go
generated
vendored
46
vendor/github.com/containers/image/docker/daemon/client.go
generated
vendored
@@ -15,10 +15,10 @@ const (
|
||||
)
|
||||
|
||||
// NewDockerClient initializes a new API client based on the passed SystemContext.
|
||||
func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
|
||||
func newDockerClient(sys *types.SystemContext) (*dockerclient.Client, error) {
|
||||
host := dockerclient.DefaultDockerHost
|
||||
if ctx != nil && ctx.DockerDaemonHost != "" {
|
||||
host = ctx.DockerDaemonHost
|
||||
if sys != nil && sys.DockerDaemonHost != "" {
|
||||
host = sys.DockerDaemonHost
|
||||
}
|
||||
|
||||
// Sadly, unix:// sockets don't work transparently with dockerclient.NewClient.
|
||||
@@ -27,32 +27,39 @@ func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
|
||||
// regardless of the values in the *tls.Config), and we would have to call sockets.ConfigureTransport.
|
||||
//
|
||||
// We don't really want to configure anything for unix:// sockets, so just pass a nil *http.Client.
|
||||
proto, _, _, err := dockerclient.ParseHost(host)
|
||||
//
|
||||
// Similarly, if we want to communicate over plain HTTP on a TCP socket, we also need to set
|
||||
// TLSClientConfig to nil. This can be achieved by using the form `http://`
|
||||
url, err := dockerclient.ParseHostURL(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var httpClient *http.Client
|
||||
if proto != "unix" {
|
||||
hc, err := tlsConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if url.Scheme != "unix" {
|
||||
if url.Scheme == "http" {
|
||||
httpClient = httpConfig()
|
||||
} else {
|
||||
hc, err := tlsConfig(sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient = hc
|
||||
}
|
||||
httpClient = hc
|
||||
}
|
||||
|
||||
return dockerclient.NewClient(host, defaultAPIVersion, httpClient, nil)
|
||||
}
|
||||
|
||||
func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
|
||||
func tlsConfig(sys *types.SystemContext) (*http.Client, error) {
|
||||
options := tlsconfig.Options{}
|
||||
if ctx != nil && ctx.DockerDaemonInsecureSkipTLSVerify {
|
||||
if sys != nil && sys.DockerDaemonInsecureSkipTLSVerify {
|
||||
options.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if ctx != nil && ctx.DockerDaemonCertPath != "" {
|
||||
options.CAFile = filepath.Join(ctx.DockerDaemonCertPath, "ca.pem")
|
||||
options.CertFile = filepath.Join(ctx.DockerDaemonCertPath, "cert.pem")
|
||||
options.KeyFile = filepath.Join(ctx.DockerDaemonCertPath, "key.pem")
|
||||
if sys != nil && sys.DockerDaemonCertPath != "" {
|
||||
options.CAFile = filepath.Join(sys.DockerDaemonCertPath, "ca.pem")
|
||||
options.CertFile = filepath.Join(sys.DockerDaemonCertPath, "cert.pem")
|
||||
options.KeyFile = filepath.Join(sys.DockerDaemonCertPath, "key.pem")
|
||||
}
|
||||
|
||||
tlsc, err := tlsconfig.Client(options)
|
||||
@@ -67,3 +74,12 @@ func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
|
||||
CheckRedirect: dockerclient.CheckRedirect,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func httpConfig() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: nil,
|
||||
},
|
||||
CheckRedirect: dockerclient.CheckRedirect,
|
||||
}
|
||||
}
|
||||
|
||||
46
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
46
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
@@ -9,11 +10,11 @@ import (
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type daemonImageDestination struct {
|
||||
ref daemonReference
|
||||
mustMatchRuntimeOS bool
|
||||
*tarfile.Destination // Implements most of types.ImageDestination
|
||||
// For talking to imageLoadGoroutine
|
||||
goroutineCancel context.CancelFunc
|
||||
@@ -24,7 +25,7 @@ type daemonImageDestination struct {
|
||||
}
|
||||
|
||||
// newImageDestination returns a types.ImageDestination for the specified image reference.
|
||||
func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
|
||||
if ref.ref == nil {
|
||||
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
||||
}
|
||||
@@ -33,7 +34,12 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
|
||||
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
||||
}
|
||||
|
||||
c, err := newDockerClient(ctx)
|
||||
var mustMatchRuntimeOS = true
|
||||
if sys != nil && sys.DockerDaemonHost != client.DefaultDockerHost {
|
||||
mustMatchRuntimeOS = false
|
||||
}
|
||||
|
||||
c, err := newDockerClient(sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error initializing docker engine client")
|
||||
}
|
||||
@@ -42,16 +48,17 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
|
||||
// Commit() may never be called, so we may never read from this channel; so, make this buffered to allow imageLoadGoroutine to write status and terminate even if we never read it.
|
||||
statusChannel := make(chan error, 1)
|
||||
|
||||
goroutineContext, goroutineCancel := context.WithCancel(context.Background())
|
||||
goroutineContext, goroutineCancel := context.WithCancel(ctx)
|
||||
go imageLoadGoroutine(goroutineContext, c, reader, statusChannel)
|
||||
|
||||
return &daemonImageDestination{
|
||||
ref: ref,
|
||||
Destination: tarfile.NewDestination(writer, namedTaggedRef),
|
||||
goroutineCancel: goroutineCancel,
|
||||
statusChannel: statusChannel,
|
||||
writer: writer,
|
||||
committed: false,
|
||||
ref: ref,
|
||||
mustMatchRuntimeOS: mustMatchRuntimeOS,
|
||||
Destination: tarfile.NewDestination(writer, namedTaggedRef),
|
||||
goroutineCancel: goroutineCancel,
|
||||
statusChannel: statusChannel,
|
||||
writer: writer,
|
||||
committed: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -78,9 +85,14 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
// DesiredLayerCompression indicates if layers must be compressed, decompressed or preserved
|
||||
func (d *daemonImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.PreserveOriginal
|
||||
}
|
||||
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
||||
func (d *daemonImageDestination) MustMatchRuntimeOS() bool {
|
||||
return true
|
||||
return d.mustMatchRuntimeOS
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
@@ -112,9 +124,9 @@ func (d *daemonImageDestination) Reference() types.ImageReference {
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *daemonImageDestination) Commit() error {
|
||||
func (d *daemonImageDestination) Commit(ctx context.Context) error {
|
||||
logrus.Debugf("docker-daemon: Closing tar stream")
|
||||
if err := d.Destination.Commit(); err != nil {
|
||||
if err := d.Destination.Commit(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.writer.Close(); err != nil {
|
||||
@@ -123,6 +135,10 @@ func (d *daemonImageDestination) Commit() error {
|
||||
d.committed = true // We may still fail, but we are done sending to imageLoadGoroutine.
|
||||
|
||||
logrus.Debugf("docker-daemon: Waiting for status")
|
||||
err := <-d.statusChannel
|
||||
return err
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-d.statusChannel:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
42
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
42
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
@@ -1,22 +1,16 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/docker/tarfile"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
|
||||
|
||||
type daemonImageSource struct {
|
||||
ref daemonReference
|
||||
*tarfile.Source // Implements most of types.ImageSource
|
||||
tarCopyPath string
|
||||
}
|
||||
|
||||
type layerInfo struct {
|
||||
@@ -33,42 +27,26 @@ type layerInfo struct {
|
||||
// (We could, perhaps, expect an exact sequence, assume that the first plaintext file
|
||||
// is the config, and that the following len(RootFS) files are the layers, but that feels
|
||||
// way too brittle.)
|
||||
func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageSource, error) {
|
||||
c, err := newDockerClient(ctx)
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref daemonReference) (types.ImageSource, error) {
|
||||
c, err := newDockerClient(sys)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error initializing docker engine client")
|
||||
}
|
||||
// Per NewReference(), ref.StringWithinTransport() is either an image ID (config digest), or a !reference.NameOnly() reference.
|
||||
// Either way ImageSave should create a tarball with exactly one image.
|
||||
inputStream, err := c.ImageSave(context.TODO(), []string{ref.StringWithinTransport()})
|
||||
inputStream, err := c.ImageSave(ctx, []string{ref.StringWithinTransport()})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error loading image from docker engine")
|
||||
}
|
||||
defer inputStream.Close()
|
||||
|
||||
// FIXME: use SystemContext here.
|
||||
tarCopyFile, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-daemon-tar")
|
||||
src, err := tarfile.NewSourceFromStream(inputStream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tarCopyFile.Close()
|
||||
|
||||
succeeded := false
|
||||
defer func() {
|
||||
if !succeeded {
|
||||
os.Remove(tarCopyFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(tarCopyFile, inputStream); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
succeeded = true
|
||||
return &daemonImageSource{
|
||||
ref: ref,
|
||||
Source: tarfile.NewSource(tarCopyFile.Name()),
|
||||
tarCopyPath: tarCopyFile.Name(),
|
||||
ref: ref,
|
||||
Source: src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -78,7 +56,7 @@ func (s *daemonImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *daemonImageSource) Close() error {
|
||||
return os.Remove(s.tarCopyPath)
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *daemonImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
68
vendor/github.com/containers/image/docker/daemon/daemon_transport.go
generated
vendored
68
vendor/github.com/containers/image/docker/daemon/daemon_transport.go
generated
vendored
@@ -1,13 +1,16 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/docker/policyconfiguration"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -34,8 +37,15 @@ func (t daemonTransport) ParseReference(reference string) (types.ImageReference,
|
||||
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
|
||||
// scope passed to this function will not be "", that value is always allowed.
|
||||
func (t daemonTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
// See the explanation in daemonReference.PolicyConfigurationIdentity.
|
||||
return errors.New(`docker-daemon: does not support any scopes except the default "" one`)
|
||||
// ID values cannot be effectively namespaced, and are clearly invalid host:port values.
|
||||
if _, err := digest.Parse(scope); err == nil {
|
||||
return errors.Errorf(`docker-daemon: can not use algo:digest value %s as a namespace`, scope)
|
||||
}
|
||||
|
||||
// FIXME? We could be verifying the various character set and length restrictions
|
||||
// from docker/distribution/reference.regexp.go, but other than that there
|
||||
// are few semantically invalid strings.
|
||||
return nil
|
||||
}
|
||||
|
||||
// daemonReference is an ImageReference for images managed by a local Docker daemon
|
||||
@@ -87,6 +97,8 @@ func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference,
|
||||
// A github.com/distribution/reference value can have a tag and a digest at the same time!
|
||||
// Most versions of docker/reference do not handle that (ignoring the tag), so reject such input.
|
||||
// This MAY be accepted in the future.
|
||||
// (Even if it were supported, the semantics of policy namespaces are unclear - should we drop
|
||||
// the tag or the digest first?)
|
||||
_, isTagged := ref.(reference.NamedTagged)
|
||||
_, isDigested := ref.(reference.Canonical)
|
||||
if isTagged && isDigested {
|
||||
@@ -136,9 +148,28 @@ func (ref daemonReference) DockerReference() reference.Named {
|
||||
// Returns "" if configuration identities for these references are not supported.
|
||||
func (ref daemonReference) PolicyConfigurationIdentity() string {
|
||||
// We must allow referring to images in the daemon by image ID, otherwise untagged images would not be accessible.
|
||||
// But the existence of image IDs means that we can’t truly well namespace the input; the untagged images would have to fall into the default policy,
|
||||
// which can be unexpected. So, punt.
|
||||
return "" // This still allows using the default "" scope to define a policy for this transport.
|
||||
// But the existence of image IDs means that we can’t truly well namespace the input:
|
||||
// a single image can be namespaced either using the name or the ID depending on how it is named.
|
||||
//
|
||||
// That’s fairly unexpected, but we have to cope somehow.
|
||||
//
|
||||
// So, use the ordinary docker/policyconfiguration namespacing for named images.
|
||||
// image IDs all fall into the root namespace.
|
||||
// Users can set up the root namespace to be either untrusted or rejected,
|
||||
// and to set up specific trust for named namespaces. This allows verifying image
|
||||
// identity when a name is known, and unnamed images would be untrusted or rejected.
|
||||
switch {
|
||||
case ref.id != "":
|
||||
return "" // This still allows using the default "" scope to define a global policy for ID-identified images.
|
||||
case ref.ref != nil:
|
||||
res, err := policyconfiguration.DockerReferenceIdentity(ref.ref)
|
||||
if res == "" || err != nil { // Coverage: Should never happen, NewReference above should refuse values which could cause a failure.
|
||||
panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err))
|
||||
}
|
||||
return res
|
||||
default: // Coverage: Should never happen, NewReference above should refuse such values.
|
||||
panic("Internal inconsistency: daemonReference has empty id and nil ref")
|
||||
}
|
||||
}
|
||||
|
||||
// PolicyConfigurationNamespaces returns a list of other policy configuration namespaces to search
|
||||
@@ -148,7 +179,14 @@ func (ref daemonReference) PolicyConfigurationIdentity() string {
|
||||
// and each following element to be a prefix of the element preceding it.
|
||||
func (ref daemonReference) PolicyConfigurationNamespaces() []string {
|
||||
// See the explanation in daemonReference.PolicyConfigurationIdentity.
|
||||
return []string{}
|
||||
switch {
|
||||
case ref.id != "":
|
||||
return []string{}
|
||||
case ref.ref != nil:
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
|
||||
default: // Coverage: Should never happen, NewReference above should refuse such values.
|
||||
panic("Internal inconsistency: daemonReference has empty id and nil ref")
|
||||
}
|
||||
}
|
||||
|
||||
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
|
||||
@@ -156,28 +194,28 @@ func (ref daemonReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref daemonReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
func (ref daemonReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, src)
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref daemonReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
func (ref daemonReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref daemonReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref daemonReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref daemonReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref daemonReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
// Should this just untag the image? Should this stop running containers?
|
||||
// The semantics is not quite as clear as for remote repositories.
|
||||
// The user can run (docker rmi) directly anyway, so, for now(?), punt instead of trying to guess what the user meant.
|
||||
|
||||
286
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
286
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
@@ -6,13 +6,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/pkg/docker/config"
|
||||
"github.com/containers/image/pkg/tlsclientconfig"
|
||||
"github.com/containers/image/types"
|
||||
@@ -24,10 +27,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
dockerHostname = "docker.io"
|
||||
dockerRegistry = "registry-1.docker.io"
|
||||
|
||||
systemPerHostCertDirPath = "/etc/docker/certs.d"
|
||||
dockerHostname = "docker.io"
|
||||
dockerV1Hostname = "index.docker.io"
|
||||
dockerRegistry = "registry-1.docker.io"
|
||||
|
||||
resolvedPingV2URL = "%s://%s/v2/"
|
||||
resolvedPingV1URL = "%s://%s/v1/_ping"
|
||||
@@ -49,6 +51,7 @@ var (
|
||||
ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||
// ErrUnauthorizedForCredentials is returned when the status code returned is 401
|
||||
ErrUnauthorizedForCredentials = errors.New("unable to retrieve auth token: invalid username/password")
|
||||
systemPerHostCertDirPaths = [2]string{"/etc/containers/certs.d", "/etc/docker/certs.d"}
|
||||
)
|
||||
|
||||
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
||||
@@ -66,15 +69,16 @@ type extensionSignatureList struct {
|
||||
}
|
||||
|
||||
type bearerToken struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
Token string `json:"token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
}
|
||||
|
||||
// dockerClient is configuration for dealing with a single Docker registry.
|
||||
type dockerClient struct {
|
||||
// The following members are set by newDockerClient and do not change afterwards.
|
||||
ctx *types.SystemContext
|
||||
sys *types.SystemContext
|
||||
registry string
|
||||
username string
|
||||
password string
|
||||
@@ -96,6 +100,37 @@ type authScope struct {
|
||||
actions string
|
||||
}
|
||||
|
||||
// sendAuth determines whether we need authentication for v2 or v1 endpoint.
|
||||
type sendAuth int
|
||||
|
||||
const (
|
||||
// v2 endpoint with authentication.
|
||||
v2Auth sendAuth = iota
|
||||
// v1 endpoint with authentication.
|
||||
// TODO: Get v1Auth working
|
||||
// v1Auth
|
||||
// no authentication, works for both v1 and v2.
|
||||
noAuth
|
||||
)
|
||||
|
||||
func newBearerTokenFromJSONBlob(blob []byte) (*bearerToken, error) {
|
||||
token := new(bearerToken)
|
||||
if err := json.Unmarshal(blob, &token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token.Token == "" {
|
||||
token.Token = token.AccessToken
|
||||
}
|
||||
if token.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
token.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", token.ExpiresIn)
|
||||
}
|
||||
if token.IssuedAt.IsZero() {
|
||||
token.IssuedAt = time.Now().UTC()
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// this is cloned from docker/go-connections because upstream docker has changed
|
||||
// it and make deps here fails otherwise.
|
||||
// We'll drop this once we upgrade to docker 1.13.x deps.
|
||||
@@ -109,40 +144,63 @@ func serverDefault() *tls.Config {
|
||||
}
|
||||
|
||||
// dockerCertDir returns a path to a directory to be consumed by tlsclientconfig.SetupCertificates() depending on ctx and hostPort.
|
||||
func dockerCertDir(ctx *types.SystemContext, hostPort string) string {
|
||||
if ctx != nil && ctx.DockerCertPath != "" {
|
||||
return ctx.DockerCertPath
|
||||
func dockerCertDir(sys *types.SystemContext, hostPort string) (string, error) {
|
||||
if sys != nil && sys.DockerCertPath != "" {
|
||||
return sys.DockerCertPath, nil
|
||||
}
|
||||
var hostCertDir string
|
||||
if ctx != nil && ctx.DockerPerHostCertDirPath != "" {
|
||||
hostCertDir = ctx.DockerPerHostCertDirPath
|
||||
} else if ctx != nil && ctx.RootForImplicitAbsolutePaths != "" {
|
||||
hostCertDir = filepath.Join(ctx.RootForImplicitAbsolutePaths, systemPerHostCertDirPath)
|
||||
} else {
|
||||
hostCertDir = systemPerHostCertDirPath
|
||||
if sys != nil && sys.DockerPerHostCertDirPath != "" {
|
||||
return filepath.Join(sys.DockerPerHostCertDirPath, hostPort), nil
|
||||
}
|
||||
return filepath.Join(hostCertDir, hostPort)
|
||||
|
||||
var (
|
||||
hostCertDir string
|
||||
fullCertDirPath string
|
||||
)
|
||||
for _, systemPerHostCertDirPath := range systemPerHostCertDirPaths {
|
||||
if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
|
||||
hostCertDir = filepath.Join(sys.RootForImplicitAbsolutePaths, systemPerHostCertDirPath)
|
||||
} else {
|
||||
hostCertDir = systemPerHostCertDirPath
|
||||
}
|
||||
|
||||
fullCertDirPath = filepath.Join(hostCertDir, hostPort)
|
||||
_, err := os.Stat(fullCertDirPath)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if os.IsPermission(err) {
|
||||
logrus.Debugf("error accessing certs directory due to permissions: %v", err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return fullCertDirPath, nil
|
||||
}
|
||||
|
||||
// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||
func newDockerClientFromRef(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
func newDockerClientFromRef(sys *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
registry := reference.Domain(ref.ref)
|
||||
username, password, err := config.GetAuthentication(ctx, reference.Domain(ref.ref))
|
||||
username, password, err := config.GetAuthentication(sys, reference.Domain(ref.ref))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting username and password")
|
||||
}
|
||||
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
||||
sigBase, err := configuredSignatureStorageBase(sys, ref, write)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteName := reference.Path(ref.ref)
|
||||
|
||||
return newDockerClientWithDetails(ctx, registry, username, password, actions, sigBase, remoteName)
|
||||
return newDockerClientWithDetails(sys, registry, username, password, actions, sigBase, remoteName)
|
||||
}
|
||||
|
||||
// newDockerClientWithDetails returns a new dockerClient instance for the given parameters
|
||||
func newDockerClientWithDetails(ctx *types.SystemContext, registry, username, password, actions string, sigBase signatureStorageBase, remoteName string) (*dockerClient, error) {
|
||||
func newDockerClientWithDetails(sys *types.SystemContext, registry, username, password, actions string, sigBase signatureStorageBase, remoteName string) (*dockerClient, error) {
|
||||
hostName := registry
|
||||
if registry == dockerHostname {
|
||||
registry = dockerRegistry
|
||||
@@ -155,17 +213,20 @@ func newDockerClientWithDetails(ctx *types.SystemContext, registry, username, pa
|
||||
// dockerHostname here, because it is more symmetrical to read the configuration in that case as well, and because
|
||||
// generally the UI hides the existence of the different dockerRegistry. But note that this behavior is
|
||||
// undocumented and may change if docker/docker changes.
|
||||
certDir := dockerCertDir(ctx, hostName)
|
||||
certDir, err := dockerCertDir(sys, hostName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := tlsclientconfig.SetupCertificates(certDir, tr.TLSClientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ctx != nil && ctx.DockerInsecureSkipTLSVerify {
|
||||
if sys != nil && sys.DockerInsecureSkipTLSVerify {
|
||||
tr.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
return &dockerClient{
|
||||
ctx: ctx,
|
||||
sys: sys,
|
||||
registry: registry,
|
||||
username: username,
|
||||
password: password,
|
||||
@@ -180,13 +241,13 @@ func newDockerClientWithDetails(ctx *types.SystemContext, registry, username, pa
|
||||
|
||||
// CheckAuth validates the credentials by attempting to log into the registry
|
||||
// returns an error if an error occcured while making the http request or the status code received was 401
|
||||
func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, password, registry string) error {
|
||||
newLoginClient, err := newDockerClientWithDetails(sCtx, registry, username, password, "", nil, "")
|
||||
func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password, registry string) error {
|
||||
newLoginClient, err := newDockerClientWithDetails(sys, registry, username, password, "", nil, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating new docker client")
|
||||
}
|
||||
|
||||
resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil)
|
||||
resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,26 +259,130 @@ func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, passwor
|
||||
case http.StatusUnauthorized:
|
||||
return ErrUnauthorizedForCredentials
|
||||
default:
|
||||
return errors.Errorf("error occured with status code %q", resp.StatusCode)
|
||||
return errors.Errorf("error occured with status code %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
// SearchResult holds the information of each matching image
|
||||
// It matches the output returned by the v1 endpoint
|
||||
type SearchResult struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
// StarCount states the number of stars the image has
|
||||
StarCount int `json:"star_count"`
|
||||
IsTrusted bool `json:"is_trusted"`
|
||||
// IsAutomated states whether the image is an automated build
|
||||
IsAutomated bool `json:"is_automated"`
|
||||
// IsOfficial states whether the image is an official build
|
||||
IsOfficial bool `json:"is_official"`
|
||||
}
|
||||
|
||||
// SearchRegistry queries a registry for images that contain "image" in their name
|
||||
// The limit is the max number of results desired
|
||||
// Note: The limit value doesn't work with all registries
|
||||
// for example registry.access.redhat.com returns all the results without limiting it to the limit value
|
||||
func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, image string, limit int) ([]SearchResult, error) {
|
||||
type V2Results struct {
|
||||
// Repositories holds the results returned by the /v2/_catalog endpoint
|
||||
Repositories []string `json:"repositories"`
|
||||
}
|
||||
type V1Results struct {
|
||||
// Results holds the results returned by the /v1/search endpoint
|
||||
Results []SearchResult `json:"results"`
|
||||
}
|
||||
v2Res := &V2Results{}
|
||||
v1Res := &V1Results{}
|
||||
|
||||
// Get credentials from authfile for the underlying hostname
|
||||
username, password, err := config.GetAuthentication(sys, registry)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting username and password")
|
||||
}
|
||||
|
||||
// The /v2/_catalog endpoint has been disabled for docker.io therefore the call made to that endpoint will fail
|
||||
// So using the v1 hostname for docker.io for simplicity of implementation and the fact that it returns search results
|
||||
if registry == dockerHostname {
|
||||
registry = dockerV1Hostname
|
||||
}
|
||||
|
||||
client, err := newDockerClientWithDetails(sys, registry, username, password, "", nil, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating new docker client")
|
||||
}
|
||||
|
||||
// Only try the v1 search endpoint if the search query is not empty. If it is
|
||||
// empty skip to the v2 endpoint.
|
||||
if image != "" {
|
||||
// set up the query values for the v1 endpoint
|
||||
u := url.URL{
|
||||
Path: "/v1/search",
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("q", image)
|
||||
q.Set("n", strconv.Itoa(limit))
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
logrus.Debugf("trying to talk to v1 search endpoint\n")
|
||||
resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth)
|
||||
if err != nil {
|
||||
logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logrus.Debugf("error getting search results from v1 endpoint %q, status code %d (%s)", registry, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
} else {
|
||||
if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v1Res.Results, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("trying to talk to v2 search endpoint\n")
|
||||
resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err)
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logrus.Errorf("error getting search results from v2 endpoint %q, status code %d (%s)", registry, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
} else {
|
||||
if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
searchRes := []SearchResult{}
|
||||
for _, repo := range v2Res.Repositories {
|
||||
if strings.Contains(repo, image) {
|
||||
res := SearchResult{
|
||||
Name: repo,
|
||||
}
|
||||
searchRes = append(searchRes, res)
|
||||
}
|
||||
}
|
||||
return searchRes, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(err, "couldn't search registry %q", registry)
|
||||
}
|
||||
|
||||
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
||||
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth) (*http.Response, error) {
|
||||
if err := c.detectProperties(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path)
|
||||
return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, true)
|
||||
return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth)
|
||||
}
|
||||
|
||||
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||
// streamLen, if not -1, specifies the length of the data expected on stream.
|
||||
// makeRequest should generally be preferred.
|
||||
// TODO(runcom): too many arguments here, use a struct
|
||||
func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, sendAuth bool) (*http.Response, error) {
|
||||
func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, url, stream)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -232,10 +397,10 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url
|
||||
req.Header.Add(n, hh)
|
||||
}
|
||||
}
|
||||
if c.ctx != nil && c.ctx.DockerRegistryUserAgent != "" {
|
||||
req.Header.Add("User-Agent", c.ctx.DockerRegistryUserAgent)
|
||||
if c.sys != nil && c.sys.DockerRegistryUserAgent != "" {
|
||||
req.Header.Add("User-Agent", c.sys.DockerRegistryUserAgent)
|
||||
}
|
||||
if sendAuth {
|
||||
if auth == v2Auth {
|
||||
if err := c.setupRequestAuth(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -301,6 +466,9 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||
}
|
||||
authReq = authReq.WithContext(ctx)
|
||||
getParams := authReq.URL.Query()
|
||||
if c.username != "" {
|
||||
getParams.Add("account", c.username)
|
||||
}
|
||||
if service != "" {
|
||||
getParams.Add("service", service)
|
||||
}
|
||||
@@ -311,6 +479,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||
if c.username != "" && c.password != "" {
|
||||
authReq.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
logrus.Debugf("%s %s", authReq.Method, authReq.URL.String())
|
||||
tr := tlsclientconfig.NewTransport()
|
||||
// TODO(runcom): insecure for now to contact the external token service
|
||||
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
@@ -326,24 +495,14 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||
case http.StatusOK:
|
||||
break
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected http code: %d, URL: %s", res.StatusCode, authReq.URL)
|
||||
return nil, errors.Errorf("unexpected http code: %d (%s), URL: %s", res.StatusCode, http.StatusText(res.StatusCode), authReq.URL)
|
||||
}
|
||||
tokenBlob, err := ioutil.ReadAll(res.Body)
|
||||
tokenBlob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxAuthTokenBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var token bearerToken
|
||||
if err := json.Unmarshal(tokenBlob, &token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||
token.ExpiresIn = minimumTokenLifetimeSeconds
|
||||
logrus.Debugf("Increasing token expiration to: %d seconds", token.ExpiresIn)
|
||||
}
|
||||
if token.IssuedAt.IsZero() {
|
||||
token.IssuedAt = time.Now().UTC()
|
||||
}
|
||||
return &token, nil
|
||||
|
||||
return newBearerTokenFromJSONBlob(tokenBlob)
|
||||
}
|
||||
|
||||
// detectProperties detects various properties of the registry.
|
||||
@@ -355,15 +514,15 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||
|
||||
ping := func(scheme string) error {
|
||||
url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry)
|
||||
resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, true)
|
||||
logrus.Debugf("Ping %s err %#v", url, err)
|
||||
resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
|
||||
if err != nil {
|
||||
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
logrus.Debugf("Ping %s status %d", url, resp.StatusCode)
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized {
|
||||
return errors.Errorf("error pinging repository, response code %d", resp.StatusCode)
|
||||
return errors.Errorf("error pinging registry %s, response code %d (%s)", c.registry, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
c.challenges = parseAuthHeader(resp.Header)
|
||||
c.scheme = scheme
|
||||
@@ -371,20 +530,20 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
err := ping("https")
|
||||
if err != nil && c.ctx != nil && c.ctx.DockerInsecureSkipTLSVerify {
|
||||
if err != nil && c.sys != nil && c.sys.DockerInsecureSkipTLSVerify {
|
||||
err = ping("http")
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "pinging docker registry returned")
|
||||
if c.ctx != nil && c.ctx.DockerDisableV1Ping {
|
||||
if c.sys != nil && c.sys.DockerDisableV1Ping {
|
||||
return err
|
||||
}
|
||||
// best effort to understand if we're talking to a V1 registry
|
||||
pingV1 := func(scheme string) bool {
|
||||
url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry)
|
||||
resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, true)
|
||||
logrus.Debugf("Ping %s err %#v", url, err)
|
||||
resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
|
||||
if err != nil {
|
||||
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
@@ -395,7 +554,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||
return true
|
||||
}
|
||||
isV1 := pingV1("https")
|
||||
if !isV1 && c.ctx != nil && c.ctx.DockerInsecureSkipTLSVerify {
|
||||
if !isV1 && c.sys != nil && c.sys.DockerInsecureSkipTLSVerify {
|
||||
isV1 = pingV1("http")
|
||||
}
|
||||
if isV1 {
|
||||
@@ -409,15 +568,16 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||
// using the original data structures.
|
||||
func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
|
||||
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest)
|
||||
res, err := c.makeRequest(ctx, "GET", path, nil, nil)
|
||||
res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, client.HandleErrorResponse(res)
|
||||
return nil, errors.Wrapf(client.HandleErrorResponse(res), "Error downloading signatures for %s in %s", manifestDigest, ref.ref.Name())
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
|
||||
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureListBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
92
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
92
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
@@ -22,12 +24,12 @@ type Image struct {
|
||||
// newImage returns a new Image interface type after setting up
|
||||
// a client to the registry hosting the given image.
|
||||
// The caller must call .Close() on the returned Image.
|
||||
func newImage(ctx *types.SystemContext, ref dockerReference) (types.ImageCloser, error) {
|
||||
s, err := newImageSource(ctx, ref)
|
||||
func newImage(ctx context.Context, sys *types.SystemContext, ref dockerReference) (types.ImageCloser, error) {
|
||||
s, err := newImageSource(sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := image.FromSource(ctx, s)
|
||||
img, err := image.FromSource(ctx, sys, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,25 +41,67 @@ func (i *Image) SourceRefFullName() string {
|
||||
return i.src.ref.ref.Name()
|
||||
}
|
||||
|
||||
// GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any.
|
||||
func (i *Image) GetRepositoryTags() ([]string, error) {
|
||||
path := fmt.Sprintf(tagsPath, reference.Path(i.src.ref.ref))
|
||||
// FIXME: Pass the context.Context
|
||||
res, err := i.src.c.makeRequest(context.TODO(), "GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// print url also
|
||||
return nil, errors.Errorf("Invalid status code returned when fetching tags list %d", res.StatusCode)
|
||||
}
|
||||
type tagsRes struct {
|
||||
Tags []string
|
||||
}
|
||||
tags := &tagsRes{}
|
||||
if err := json.NewDecoder(res.Body).Decode(tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tags.Tags, nil
|
||||
// GetRepositoryTags list all tags available in the repository. The tag
|
||||
// provided inside the ImageReference will be ignored. (This is a
|
||||
// backward-compatible shim method which calls the module-level
|
||||
// GetRepositoryTags)
|
||||
func (i *Image) GetRepositoryTags(ctx context.Context) ([]string, error) {
|
||||
return GetRepositoryTags(ctx, i.src.c.sys, i.src.ref)
|
||||
}
|
||||
|
||||
// GetRepositoryTags list all tags available in the repository. The tag
|
||||
// provided inside the ImageReference will be ignored.
|
||||
func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.ImageReference) ([]string, error) {
|
||||
dr, ok := ref.(dockerReference)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("ref must be a dockerReference")
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(tagsPath, reference.Path(dr.ref))
|
||||
client, err := newDockerClientFromRef(sys, dr, false, "pull")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create client")
|
||||
}
|
||||
|
||||
tags := make([]string, 0)
|
||||
|
||||
for {
|
||||
res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// print url also
|
||||
return nil, errors.Errorf("Invalid status code returned when fetching tags list %d (%s)", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
|
||||
var tagsHolder struct {
|
||||
Tags []string
|
||||
}
|
||||
if err = json.NewDecoder(res.Body).Decode(&tagsHolder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags = append(tags, tagsHolder.Tags...)
|
||||
|
||||
link := res.Header.Get("Link")
|
||||
if link == "" {
|
||||
break
|
||||
}
|
||||
|
||||
linkURLStr := strings.Trim(strings.Split(link, ";")[0], "<>")
|
||||
linkURL, err := url.Parse(linkURLStr)
|
||||
if err != nil {
|
||||
return tags, err
|
||||
}
|
||||
|
||||
// can be relative or absolute, but we only want the path (and I
|
||||
// guess we're in trouble if it forwards to a new place...)
|
||||
path = linkURL.Path
|
||||
if linkURL.RawQuery != "" {
|
||||
path += "?"
|
||||
path += linkURL.RawQuery
|
||||
}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
69
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
69
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
@@ -14,6 +14,7 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
@@ -33,8 +34,8 @@ type dockerImageDestination struct {
|
||||
}
|
||||
|
||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||
c, err := newDockerClientFromRef(ctx, ref, true, "pull,push")
|
||||
func newImageDestination(sys *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||
c, err := newDockerClientFromRef(sys, ref, true, "pull,push")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,8 +67,8 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *dockerImageDestination) SupportsSignatures() error {
|
||||
if err := d.c.detectProperties(context.TODO()); err != nil {
|
||||
func (d *dockerImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
if err := d.c.detectProperties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
@@ -80,9 +81,8 @@ func (d *dockerImageDestination) SupportsSignatures() error {
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *dockerImageDestination) ShouldCompressLayers() bool {
|
||||
return true
|
||||
func (d *dockerImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.Compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -96,6 +96,13 @@ func (d *dockerImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *dockerImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // We do want the manifest updated; older registry versions refuse manifests if the embedded reference does not match.
|
||||
}
|
||||
|
||||
// sizeCounter is an io.Writer which only counts the total size of its input.
|
||||
type sizeCounter struct{ size int64 }
|
||||
|
||||
@@ -110,9 +117,9 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
if inputInfo.Digest.String() != "" {
|
||||
haveBlob, size, err := d.HasBlob(inputInfo)
|
||||
haveBlob, size, err := d.HasBlob(ctx, inputInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
@@ -124,14 +131,14 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
// FIXME? Chunked upload, progress reporting, etc.
|
||||
uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref))
|
||||
logrus.Debugf("Uploading %s", uploadPath)
|
||||
res, err := d.c.makeRequest(context.TODO(), "POST", uploadPath, nil, nil)
|
||||
res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusAccepted {
|
||||
logrus.Debugf("Error initiating layer upload, response %#v", *res)
|
||||
return types.BlobInfo{}, errors.Errorf("Error initiating layer upload to %s, status %d", uploadPath, res.StatusCode)
|
||||
return types.BlobInfo{}, errors.Wrapf(client.HandleErrorResponse(res), "Error initiating layer upload to %s in %s", uploadPath, d.c.registry)
|
||||
}
|
||||
uploadLocation, err := res.Location()
|
||||
if err != nil {
|
||||
@@ -141,7 +148,7 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
digester := digest.Canonical.Digester()
|
||||
sizeCounter := &sizeCounter{}
|
||||
tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter))
|
||||
res, err = d.c.makeRequestToResolvedURL(context.TODO(), "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, true)
|
||||
res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error uploading layer chunked, response %#v", res)
|
||||
return types.BlobInfo{}, err
|
||||
@@ -160,14 +167,14 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
// TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717
|
||||
locationQuery.Set("digest", computedDigest.String())
|
||||
uploadLocation.RawQuery = locationQuery.Encode()
|
||||
res, err = d.c.makeRequestToResolvedURL(context.TODO(), "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, true)
|
||||
res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
logrus.Debugf("Error uploading layer, response %#v", *res)
|
||||
return types.BlobInfo{}, errors.Errorf("Error uploading layer to %s, status %d", uploadLocation, res.StatusCode)
|
||||
return types.BlobInfo{}, errors.Wrapf(client.HandleErrorResponse(res), "Error uploading layer to %s", uploadLocation)
|
||||
}
|
||||
|
||||
logrus.Debugf("Upload of layer %s complete", computedDigest)
|
||||
@@ -178,14 +185,14 @@ func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
func (d *dockerImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
checkPath := fmt.Sprintf(blobsPath, reference.Path(d.ref.ref), info.Digest.String())
|
||||
|
||||
logrus.Debugf("Checking %s", checkPath)
|
||||
res, err := d.c.makeRequest(context.TODO(), "HEAD", checkPath, nil, nil)
|
||||
res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
@@ -196,16 +203,16 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
||||
return true, getBlobSize(res), nil
|
||||
case http.StatusUnauthorized:
|
||||
logrus.Debugf("... not authorized")
|
||||
return false, -1, errors.Errorf("not authorized to read from destination repository %s", reference.Path(d.ref.ref))
|
||||
return false, -1, errors.Wrapf(client.HandleErrorResponse(res), "Error checking whether a blob %s exists in %s", info.Digest, d.ref.ref.Name())
|
||||
case http.StatusNotFound:
|
||||
logrus.Debugf("... not present")
|
||||
return false, -1, nil
|
||||
default:
|
||||
return false, -1, errors.Errorf("failed to read from destination repository %s: %v", reference.Path(d.ref.ref), http.StatusText(res.StatusCode))
|
||||
return false, -1, errors.Errorf("failed to read from destination repository %s: %d (%s)", reference.Path(d.ref.ref), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *dockerImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -213,7 +220,7 @@ func (d *dockerImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInf
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *dockerImageDestination) PutManifest(m []byte) error {
|
||||
func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) error {
|
||||
digest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -231,13 +238,13 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
||||
if mimeType != "" {
|
||||
headers["Content-Type"] = []string{mimeType}
|
||||
}
|
||||
res, err := d.c.makeRequest(context.TODO(), "PUT", path, headers, bytes.NewReader(m))
|
||||
res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if !successStatus(res.StatusCode) {
|
||||
err = errors.Wrapf(client.HandleErrorResponse(res), "Error uploading manifest to %s", path)
|
||||
err = errors.Wrapf(client.HandleErrorResponse(res), "Error uploading manifest %s to %s", refTail, d.ref.ref.Name())
|
||||
if isManifestInvalidError(errors.Cause(err)) {
|
||||
err = types.ManifestTypeRejectedError{Err: err}
|
||||
}
|
||||
@@ -268,19 +275,19 @@ func isManifestInvalidError(err error) bool {
|
||||
return ec.ErrorCode() == v2.ErrorCodeManifestInvalid || ec.ErrorCode() == v2.ErrorCodeTagInvalid
|
||||
}
|
||||
|
||||
func (d *dockerImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
// Do not fail if we don’t really need to support signatures.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := d.c.detectProperties(context.TODO()); err != nil {
|
||||
if err := d.c.detectProperties(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case d.c.signatureBase != nil:
|
||||
return d.putSignaturesToLookaside(signatures)
|
||||
case d.c.supportsSignatures:
|
||||
return d.putSignaturesToAPIExtension(signatures)
|
||||
return d.putSignaturesToAPIExtension(ctx, signatures)
|
||||
default:
|
||||
return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured")
|
||||
}
|
||||
@@ -379,7 +386,7 @@ func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error
|
||||
}
|
||||
|
||||
// putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||
func (d *dockerImageDestination) putSignaturesToAPIExtension(signatures [][]byte) error {
|
||||
func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte) error {
|
||||
// Skip dealing with the manifest digest, or reading the old state, if not necessary.
|
||||
if len(signatures) == 0 {
|
||||
return nil
|
||||
@@ -394,7 +401,7 @@ func (d *dockerImageDestination) putSignaturesToAPIExtension(signatures [][]byte
|
||||
// always adds signatures. Eventually we should also allow removing signatures,
|
||||
// but the X-Registry-Supports-Signatures API extension does not support that yet.
|
||||
|
||||
existingSignatures, err := d.c.getExtensionsSignatures(context.TODO(), d.ref, d.manifestDigest)
|
||||
existingSignatures, err := d.c.getExtensionsSignatures(ctx, d.ref, d.manifestDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -436,18 +443,18 @@ sigExists:
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String())
|
||||
res, err := d.c.makeRequest(context.TODO(), "PUT", path, nil, bytes.NewReader(body))
|
||||
res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxErrorBodySize)
|
||||
if err == nil {
|
||||
logrus.Debugf("Error body %s", string(body))
|
||||
}
|
||||
logrus.Debugf("Error uploading signature, status %d, %#v", res.StatusCode, res)
|
||||
return errors.Errorf("Error uploading signature to %s, status %d", path, res.StatusCode)
|
||||
return errors.Wrapf(client.HandleErrorResponse(res), "Error uploading signature to %s in %s", path, d.c.registry)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,6 +465,6 @@ sigExists:
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *dockerImageDestination) Commit() error {
|
||||
func (d *dockerImageDestination) Commit(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
62
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
62
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@@ -12,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
@@ -30,8 +31,8 @@ type dockerImageSource struct {
|
||||
|
||||
// newImageSource creates a new ImageSource for the specified image reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
||||
c, err := newDockerClientFromRef(ctx, ref, false, "pull")
|
||||
func newImageSource(sys *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
||||
c, err := newDockerClientFromRef(sys, ref, false, "pull")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -52,6 +53,11 @@ func (s *dockerImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *dockerImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
|
||||
// Alternatively, an empty string is returned unchanged, and invalid values are "simplified" to an empty string.
|
||||
func simplifyContentType(contentType string) string {
|
||||
@@ -69,11 +75,11 @@ func simplifyContentType(contentType string) string {
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *dockerImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
func (s *dockerImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return s.fetchManifest(context.TODO(), instanceDigest.String())
|
||||
return s.fetchManifest(ctx, instanceDigest.String())
|
||||
}
|
||||
err := s.ensureManifestIsLoaded(context.TODO())
|
||||
err := s.ensureManifestIsLoaded(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -84,15 +90,16 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
|
||||
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
|
||||
headers := make(map[string][]string)
|
||||
headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes
|
||||
res, err := s.c.makeRequest(ctx, "GET", path, headers, nil)
|
||||
res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, "", client.HandleErrorResponse(res)
|
||||
return nil, "", errors.Wrapf(client.HandleErrorResponse(res), "Error reading manifest %s in %s", tagOrDigest, s.ref.ref.Name())
|
||||
}
|
||||
manblob, err := ioutil.ReadAll(res.Body)
|
||||
|
||||
manblob, err := iolimits.ReadAtMost(res.Body, iolimits.MaxManifestBodySize)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -103,7 +110,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
|
||||
//
|
||||
// ImageSource implementations are not required or expected to do any caching,
|
||||
// but because our signatures are “attached” to the manifest digest,
|
||||
// we need to ensure that the digest of the manifest returned by GetManifest(nil)
|
||||
// we need to ensure that the digest of the manifest returned by GetManifest(ctx, nil)
|
||||
// and used by GetSignatures(ctx, nil) are consistent, otherwise we would get spurious
|
||||
// signature verification failures when pulling while a tag is being updated.
|
||||
func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error {
|
||||
@@ -126,16 +133,16 @@ func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dockerImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, error) {
|
||||
func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) {
|
||||
var (
|
||||
resp *http.Response
|
||||
err error
|
||||
)
|
||||
for _, url := range urls {
|
||||
resp, err = s.c.makeRequestToResolvedURL(context.TODO(), "GET", url, nil, nil, -1, false)
|
||||
resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
|
||||
if err == nil {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = errors.Errorf("error fetching external blob from %q: %d", url, resp.StatusCode)
|
||||
err = errors.Errorf("error fetching external blob from %q: %d (%s)", url, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
logrus.Debug(err)
|
||||
continue
|
||||
}
|
||||
@@ -157,20 +164,20 @@ func getBlobSize(resp *http.Response) int64 {
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if len(info.URLs) != 0 {
|
||||
return s.getExternalBlob(info.URLs)
|
||||
return s.getExternalBlob(ctx, info.URLs)
|
||||
}
|
||||
|
||||
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
|
||||
logrus.Debugf("Downloading %s", path)
|
||||
res, err := s.c.makeRequest(context.TODO(), "GET", path, nil, nil)
|
||||
res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// print url also
|
||||
return nil, 0, errors.Errorf("Invalid status code returned when fetching blob %d", res.StatusCode)
|
||||
return nil, 0, errors.Errorf("Invalid status code returned when fetching blob %d (%s)", res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
return res.Body, getBlobSize(res), nil
|
||||
}
|
||||
@@ -269,9 +276,9 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return nil, true, nil
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
return nil, false, errors.Errorf("Error reading signature from %s: status %d", url.String(), res.StatusCode)
|
||||
return nil, false, errors.Errorf("Error reading signature from %s: status %d (%s)", url.String(), res.StatusCode, http.StatusText(res.StatusCode))
|
||||
}
|
||||
sig, err := ioutil.ReadAll(res.Body)
|
||||
sig, err := iolimits.ReadAtMost(res.Body, iolimits.MaxSignatureBodySize)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -304,8 +311,15 @@ func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, i
|
||||
}
|
||||
|
||||
// deleteImage deletes the named image from the registry, if supported.
|
||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||
c, err := newDockerClientFromRef(ctx, ref, true, "push")
|
||||
func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerReference) error {
|
||||
// docker/distribution does not document what action should be used for deleting images.
|
||||
//
|
||||
// Current docker/distribution requires "pull" for reading the manifest and "delete" for deleting it.
|
||||
// quay.io requires "push" (an explicit "pull" is unnecessary), does not grant any token (fails parsing the request) if "delete" is included.
|
||||
// OpenShift ignores the action string (both the password and the token is an OpenShift API token identifying a user).
|
||||
//
|
||||
// We have to hard-code a single string, luckily both docker/distribution and quay.io support "*" to mean "everything".
|
||||
c, err := newDockerClientFromRef(sys, ref, true, "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,12 +334,12 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||
return err
|
||||
}
|
||||
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
|
||||
get, err := c.makeRequest(context.TODO(), "GET", getPath, headers, nil)
|
||||
get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer get.Body.Close()
|
||||
manifestBody, err := ioutil.ReadAll(get.Body)
|
||||
manifestBody, err := iolimits.ReadAtMost(get.Body, iolimits.MaxManifestBodySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -342,13 +356,13 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||
|
||||
// When retrieving the digest from a registry >= 2.3 use the following header:
|
||||
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
|
||||
delete, err := c.makeRequest(context.TODO(), "DELETE", deletePath, headers, nil)
|
||||
delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer delete.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(delete.Body)
|
||||
body, err := iolimits.ReadAtMost(delete.Body, iolimits.MaxErrorBodySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
17
vendor/github.com/containers/image/docker/docker_transport.go
generated
vendored
17
vendor/github.com/containers/image/docker/docker_transport.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -127,25 +128,25 @@ func (ref dockerReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref dockerReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
return newImage(ctx, ref)
|
||||
func (ref dockerReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
return newImage(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref dockerReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
func (ref dockerReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(sys, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref dockerReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref dockerReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref dockerReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
return deleteImage(ctx, ref)
|
||||
func (ref dockerReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return deleteImage(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// tagOrDigest returns a tag or digest from the reference.
|
||||
|
||||
16
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
16
vendor/github.com/containers/image/docker/lookaside.go
generated
vendored
@@ -45,9 +45,9 @@ type registryNamespace struct {
|
||||
type signatureStorageBase *url.URL // The only documented value is nil, meaning storage is not supported.
|
||||
|
||||
// configuredSignatureStorageBase reads configuration to find an appropriate signature storage URL for ref, for write access if “write”.
|
||||
func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReference, write bool) (signatureStorageBase, error) {
|
||||
func configuredSignatureStorageBase(sys *types.SystemContext, ref dockerReference, write bool) (signatureStorageBase, error) {
|
||||
// FIXME? Loading and parsing the config could be cached across calls.
|
||||
dirPath := registriesDirPath(ctx)
|
||||
dirPath := registriesDirPath(sys)
|
||||
logrus.Debugf(`Using registries.d directory %s for sigstore configuration`, dirPath)
|
||||
config, err := loadAndMergeConfig(dirPath)
|
||||
if err != nil {
|
||||
@@ -74,13 +74,13 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc
|
||||
}
|
||||
|
||||
// registriesDirPath returns a path to registries.d
|
||||
func registriesDirPath(ctx *types.SystemContext) string {
|
||||
if ctx != nil {
|
||||
if ctx.RegistriesDirPath != "" {
|
||||
return ctx.RegistriesDirPath
|
||||
func registriesDirPath(sys *types.SystemContext) string {
|
||||
if sys != nil {
|
||||
if sys.RegistriesDirPath != "" {
|
||||
return sys.RegistriesDirPath
|
||||
}
|
||||
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(ctx.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
|
||||
if sys.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
|
||||
}
|
||||
}
|
||||
return systemRegistriesDirPath
|
||||
|
||||
290
vendor/github.com/containers/image/docker/tarfile/dest.go
generated
vendored
290
vendor/github.com/containers/image/docker/tarfile/dest.go
generated
vendored
@@ -3,14 +3,18 @@ package tarfile
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -18,42 +22,33 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
|
||||
|
||||
// Destination is a partial implementation of types.ImageDestination for writing to an io.Writer.
|
||||
type Destination struct {
|
||||
writer io.Writer
|
||||
tar *tar.Writer
|
||||
repoTag string
|
||||
writer io.Writer
|
||||
tar *tar.Writer
|
||||
repoTags []reference.NamedTagged
|
||||
// Other state.
|
||||
blobs map[digest.Digest]types.BlobInfo // list of already-sent blobs
|
||||
blobs map[digest.Digest]types.BlobInfo // list of already-sent blobs
|
||||
config []byte
|
||||
}
|
||||
|
||||
// NewDestination returns a tarfile.Destination for the specified io.Writer.
|
||||
func NewDestination(dest io.Writer, ref reference.NamedTagged) *Destination {
|
||||
// For github.com/docker/docker consumers, this works just as well as
|
||||
// refString := ref.String()
|
||||
// because when reading the RepoTags strings, github.com/docker/docker/reference
|
||||
// normalizes both of them to the same value.
|
||||
//
|
||||
// Doing it this way to include the normalized-out `docker.io[/library]` does make
|
||||
// a difference for github.com/projectatomic/docker consumers, with the
|
||||
// “Add --add-registry and --block-registry options to docker daemon” patch.
|
||||
// These consumers treat reference strings which include a hostname and reference
|
||||
// strings without a hostname differently.
|
||||
//
|
||||
// Using the host name here is more explicit about the intent, and it has the same
|
||||
// effect as (docker pull) in projectatomic/docker, which tags the result using
|
||||
// a hostname-qualified reference.
|
||||
// See https://github.com/containers/image/issues/72 for a more detailed
|
||||
// analysis and explanation.
|
||||
refString := fmt.Sprintf("%s:%s", ref.Name(), ref.Tag())
|
||||
return &Destination{
|
||||
writer: dest,
|
||||
tar: tar.NewWriter(dest),
|
||||
repoTag: refString,
|
||||
blobs: make(map[digest.Digest]types.BlobInfo),
|
||||
repoTags := []reference.NamedTagged{}
|
||||
if ref != nil {
|
||||
repoTags = append(repoTags, ref)
|
||||
}
|
||||
return &Destination{
|
||||
writer: dest,
|
||||
tar: tar.NewWriter(dest),
|
||||
repoTags: repoTags,
|
||||
blobs: make(map[digest.Digest]types.BlobInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// AddRepoTags adds the specified tags to the destination's repoTags.
|
||||
func (d *Destination) AddRepoTags(tags []reference.NamedTagged) {
|
||||
d.repoTags = append(d.repoTags, tags...)
|
||||
}
|
||||
|
||||
// SupportedManifestMIMETypes tells which manifest mime types the destination supports
|
||||
@@ -66,15 +61,10 @@ func (d *Destination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *Destination) SupportsSignatures() error {
|
||||
func (d *Destination) SupportsSignatures(ctx context.Context) error {
|
||||
return errors.Errorf("Storing signatures for docker tar files is not supported")
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *Destination) ShouldCompressLayers() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
// uploaded to the image destination, true otherwise.
|
||||
func (d *Destination) AcceptsForeignLayerURLs() bool {
|
||||
@@ -86,35 +76,35 @@ func (d *Destination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *Destination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, we only accept schema2 images where EmbeddedDockerReferenceConflicts() is always false.
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
if inputInfo.Digest.String() == "" {
|
||||
return types.BlobInfo{}, errors.Errorf("Can not stream a blob with unknown digest to docker tarfile")
|
||||
}
|
||||
|
||||
ok, size, err := d.HasBlob(inputInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
if ok {
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil
|
||||
}
|
||||
|
||||
if inputInfo.Size == -1 { // Ouch, we need to stream the blob into a temporary file just to determine the size.
|
||||
func (d *Destination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
// Ouch, we need to stream the blob into a temporary file just to determine the size.
|
||||
// When the layer is decompressed, we also have to generate the digest on uncompressed datas.
|
||||
if inputInfo.Size == -1 || inputInfo.Digest.String() == "" {
|
||||
logrus.Debugf("docker tarfile: input with unknown size, streaming to disk first ...")
|
||||
streamCopy, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-tarfile-blob")
|
||||
streamCopy, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-tarfile-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
defer os.Remove(streamCopy.Name())
|
||||
defer streamCopy.Close()
|
||||
|
||||
size, err := io.Copy(streamCopy, stream)
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
size, err := io.Copy(streamCopy, tee)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
@@ -123,17 +113,43 @@ func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
inputInfo.Size = size // inputInfo is a struct, so we are only modifying our copy.
|
||||
if inputInfo.Digest == "" {
|
||||
inputInfo.Digest = digester.Digest()
|
||||
}
|
||||
stream = streamCopy
|
||||
logrus.Debugf("... streaming done")
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
if err := d.sendFile(inputInfo.Digest.String(), inputInfo.Size, tee); err != nil {
|
||||
// Maybe the blob has been already sent
|
||||
ok, size, err := d.HasBlob(ctx, inputInfo)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
d.blobs[inputInfo.Digest] = types.BlobInfo{Digest: digester.Digest(), Size: inputInfo.Size}
|
||||
return types.BlobInfo{Digest: digester.Digest(), Size: inputInfo.Size}, nil
|
||||
if ok {
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil
|
||||
}
|
||||
|
||||
if isConfig {
|
||||
buf, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "Error reading Config file stream")
|
||||
}
|
||||
d.config = buf
|
||||
if err := d.sendFile(inputInfo.Digest.Hex()+".json", inputInfo.Size, bytes.NewReader(buf)); err != nil {
|
||||
return types.BlobInfo{}, errors.Wrap(err, "Error writing Config file")
|
||||
}
|
||||
} else {
|
||||
// Note that this can't be e.g. filepath.Join(l.Digest.Hex(), legacyLayerFileName); due to the way
|
||||
// writeLegacyLayerMetadata constructs layer IDs differently from inputinfo.Digest values (as described
|
||||
// inside it), most of the layers would end up in subdirectories alone without any metadata; (docker load)
|
||||
// tries to load every subdirectory as an image and fails if the config is missing. So, keep the layers
|
||||
// in the root of the tarball.
|
||||
if err := d.sendFile(inputInfo.Digest.Hex()+".tar", inputInfo.Size, stream); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
}
|
||||
d.blobs[inputInfo.Digest] = types.BlobInfo{Digest: inputInfo.Digest, Size: inputInfo.Size}
|
||||
return types.BlobInfo{Digest: inputInfo.Digest, Size: inputInfo.Size}, nil
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with
|
||||
@@ -142,7 +158,7 @@ func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types
|
||||
// the blob must also be returned. If the destination does not contain the
|
||||
// blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil); it
|
||||
// returns a non-nil error only on an unexpected failure.
|
||||
func (d *Destination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
func (d *Destination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf("Can not check for a blob with unknown digest")
|
||||
}
|
||||
@@ -157,18 +173,38 @@ func (d *Destination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
// returned false. Like HasBlob and unlike PutBlob, the digest can not be
|
||||
// empty. If the blob is a filesystem layer, this signifies that the changes
|
||||
// it describes need to be applied again when composing a filesystem tree.
|
||||
func (d *Destination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *Destination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (d *Destination) createRepositoriesFile(rootLayerID string) error {
|
||||
repositories := map[string]map[string]string{}
|
||||
for _, repoTag := range d.repoTags {
|
||||
if val, ok := repositories[repoTag.Name()]; ok {
|
||||
val[repoTag.Tag()] = rootLayerID
|
||||
} else {
|
||||
repositories[repoTag.Name()] = map[string]string{repoTag.Tag(): rootLayerID}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(repositories)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error marshaling repositories")
|
||||
}
|
||||
if err := d.sendBytes(legacyRepositoriesFileName, b); err != nil {
|
||||
return errors.Wrap(err, "Error writing config json file")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination.
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *Destination) PutManifest(m []byte) error {
|
||||
func (d *Destination) PutManifest(ctx context.Context, m []byte) error {
|
||||
// We do not bother with types.ManifestTypeRejectedError; our .SupportedManifestMIMETypes() above is already providing only one alternative,
|
||||
// so the caller trying a different manifest kind would be pointless.
|
||||
var man schema2Manifest
|
||||
var man manifest.Schema2
|
||||
if err := json.Unmarshal(m, &man); err != nil {
|
||||
return errors.Wrap(err, "Error parsing manifest")
|
||||
}
|
||||
@@ -176,14 +212,42 @@ func (d *Destination) PutManifest(m []byte) error {
|
||||
return errors.Errorf("Unsupported manifest type, need a Docker schema 2 manifest")
|
||||
}
|
||||
|
||||
layerPaths := []string{}
|
||||
for _, l := range man.Layers {
|
||||
layerPaths = append(layerPaths, l.Digest.String())
|
||||
layerPaths, lastLayerID, err := d.writeLegacyLayerMetadata(man.LayersDescriptors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(man.LayersDescriptors) > 0 {
|
||||
if err := d.createRepositoriesFile(lastLayerID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
repoTags := []string{}
|
||||
for _, tag := range d.repoTags {
|
||||
// For github.com/docker/docker consumers, this works just as well as
|
||||
// refString := ref.String()
|
||||
// because when reading the RepoTags strings, github.com/docker/docker/reference
|
||||
// normalizes both of them to the same value.
|
||||
//
|
||||
// Doing it this way to include the normalized-out `docker.io[/library]` does make
|
||||
// a difference for github.com/projectatomic/docker consumers, with the
|
||||
// “Add --add-registry and --block-registry options to docker daemon” patch.
|
||||
// These consumers treat reference strings which include a hostname and reference
|
||||
// strings without a hostname differently.
|
||||
//
|
||||
// Using the host name here is more explicit about the intent, and it has the same
|
||||
// effect as (docker pull) in projectatomic/docker, which tags the result using
|
||||
// a hostname-qualified reference.
|
||||
// See https://github.com/containers/image/issues/72 for a more detailed
|
||||
// analysis and explanation.
|
||||
refString := fmt.Sprintf("%s:%s", tag.Name(), tag.Tag())
|
||||
repoTags = append(repoTags, refString)
|
||||
}
|
||||
|
||||
items := []ManifestItem{{
|
||||
Config: man.Config.Digest.String(),
|
||||
RepoTags: []string{d.repoTag},
|
||||
Config: man.ConfigDescriptor.Digest.Hex() + ".json",
|
||||
RepoTags: repoTags,
|
||||
Layers: layerPaths,
|
||||
Parent: "",
|
||||
LayerSources: nil,
|
||||
@@ -194,12 +258,81 @@ func (d *Destination) PutManifest(m []byte) error {
|
||||
}
|
||||
|
||||
// FIXME? Do we also need to support the legacy format?
|
||||
return d.sendFile(manifestFileName, int64(len(itemsBytes)), bytes.NewReader(itemsBytes))
|
||||
return d.sendBytes(manifestFileName, itemsBytes)
|
||||
}
|
||||
|
||||
// writeLegacyLayerMetadata writes legacy VERSION and configuration files for all layers
|
||||
func (d *Destination) writeLegacyLayerMetadata(layerDescriptors []manifest.Schema2Descriptor) (layerPaths []string, lastLayerID string, err error) {
|
||||
var chainID digest.Digest
|
||||
lastLayerID = ""
|
||||
for i, l := range layerDescriptors {
|
||||
// This chainID value matches the computation in docker/docker/layer.CreateChainID …
|
||||
if chainID == "" {
|
||||
chainID = l.Digest
|
||||
} else {
|
||||
chainID = digest.Canonical.FromString(chainID.String() + " " + l.Digest.String())
|
||||
}
|
||||
// … but note that this image ID does not match docker/docker/image/v1.CreateID. At least recent
|
||||
// versions allocate new IDs on load, as long as the IDs we use are unique / cannot loop.
|
||||
//
|
||||
// Overall, the goal of computing a digest dependent on the full history is to avoid reusing an image ID
|
||||
// (and possibly creating a loop in the "parent" links) if a layer with the same DiffID appears two or more
|
||||
// times in layersDescriptors. The ChainID values are sufficient for this, the v1.CreateID computation
|
||||
// which also mixes in the full image configuration seems unnecessary, at least as long as we are storing
|
||||
// only a single image per tarball, i.e. all DiffID prefixes are unique (can’t differ only with
|
||||
// configuration).
|
||||
layerID := chainID.Hex()
|
||||
|
||||
physicalLayerPath := l.Digest.Hex() + ".tar"
|
||||
// The layer itself has been stored into physicalLayerPath in PutManifest.
|
||||
// So, use that path for layerPaths used in the non-legacy manifest
|
||||
layerPaths = append(layerPaths, physicalLayerPath)
|
||||
// ... and create a symlink for the legacy format;
|
||||
if err := d.sendSymlink(filepath.Join(layerID, legacyLayerFileName), filepath.Join("..", physicalLayerPath)); err != nil {
|
||||
return nil, "", errors.Wrap(err, "Error creating layer symbolic link")
|
||||
}
|
||||
|
||||
b := []byte("1.0")
|
||||
if err := d.sendBytes(filepath.Join(layerID, legacyVersionFileName), b); err != nil {
|
||||
return nil, "", errors.Wrap(err, "Error writing VERSION file")
|
||||
}
|
||||
|
||||
// The legacy format requires a config file per layer
|
||||
layerConfig := make(map[string]interface{})
|
||||
layerConfig["id"] = layerID
|
||||
|
||||
// The root layer doesn't have any parent
|
||||
if lastLayerID != "" {
|
||||
layerConfig["parent"] = lastLayerID
|
||||
}
|
||||
// The root layer configuration file is generated by using subpart of the image configuration
|
||||
if i == len(layerDescriptors)-1 {
|
||||
var config map[string]*json.RawMessage
|
||||
err := json.Unmarshal(d.config, &config)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "Error unmarshaling config")
|
||||
}
|
||||
for _, attr := range [7]string{"architecture", "config", "container", "container_config", "created", "docker_version", "os"} {
|
||||
layerConfig[attr] = config[attr]
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(layerConfig)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "Error marshaling layer config")
|
||||
}
|
||||
if err := d.sendBytes(filepath.Join(layerID, legacyConfigFileName), b); err != nil {
|
||||
return nil, "", errors.Wrap(err, "Error writing config json file")
|
||||
}
|
||||
|
||||
lastLayerID = layerID
|
||||
}
|
||||
return layerPaths, lastLayerID, nil
|
||||
}
|
||||
|
||||
type tarFI struct {
|
||||
path string
|
||||
size int64
|
||||
path string
|
||||
size int64
|
||||
isSymlink bool
|
||||
}
|
||||
|
||||
func (t *tarFI) Name() string {
|
||||
@@ -209,6 +342,9 @@ func (t *tarFI) Size() int64 {
|
||||
return t.size
|
||||
}
|
||||
func (t *tarFI) Mode() os.FileMode {
|
||||
if t.isSymlink {
|
||||
return os.ModeSymlink
|
||||
}
|
||||
return 0444
|
||||
}
|
||||
func (t *tarFI) ModTime() time.Time {
|
||||
@@ -221,6 +357,21 @@ func (t *tarFI) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendSymlink sends a symlink into the tar stream.
|
||||
func (d *Destination) sendSymlink(path string, target string) error {
|
||||
hdr, err := tar.FileInfoHeader(&tarFI{path: path, size: 0, isSymlink: true}, target)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
logrus.Debugf("Sending as tar link %s -> %s", path, target)
|
||||
return d.tar.WriteHeader(hdr)
|
||||
}
|
||||
|
||||
// sendBytes sends a path into the tar stream.
|
||||
func (d *Destination) sendBytes(path string, b []byte) error {
|
||||
return d.sendFile(path, int64(len(b)), bytes.NewReader(b))
|
||||
}
|
||||
|
||||
// sendFile sends a file into the tar stream.
|
||||
func (d *Destination) sendFile(path string, expectedSize int64, stream io.Reader) error {
|
||||
hdr, err := tar.FileInfoHeader(&tarFI{path: path, size: expectedSize}, "")
|
||||
@@ -231,6 +382,7 @@ func (d *Destination) sendFile(path string, expectedSize int64, stream io.Reader
|
||||
if err := d.tar.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using a context.Context.
|
||||
size, err := io.Copy(d.tar, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -244,7 +396,7 @@ func (d *Destination) sendFile(path string, expectedSize int64, stream io.Reader
|
||||
// PutSignatures adds the given signatures to the docker tarfile (currently not
|
||||
// supported). MUST be called after PutManifest (signatures reference manifest
|
||||
// contents)
|
||||
func (d *Destination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
if len(signatures) != 0 {
|
||||
return errors.Errorf("Storing signatures for docker tar files is not supported")
|
||||
}
|
||||
@@ -253,6 +405,6 @@ func (d *Destination) PutSignatures(signatures [][]byte) error {
|
||||
|
||||
// Commit finishes writing data to the underlying io.Writer.
|
||||
// It is the caller's responsibility to close it, if necessary.
|
||||
func (d *Destination) Commit() error {
|
||||
func (d *Destination) Commit(ctx context.Context) error {
|
||||
return d.tar.Close()
|
||||
}
|
||||
|
||||
192
vendor/github.com/containers/image/docker/tarfile/src.go
generated
vendored
192
vendor/github.com/containers/image/docker/tarfile/src.go
generated
vendored
@@ -10,6 +10,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/compression"
|
||||
"github.com/containers/image/types"
|
||||
@@ -19,13 +21,14 @@ import (
|
||||
|
||||
// Source is a partial implementation of types.ImageSource for reading from tarPath.
|
||||
type Source struct {
|
||||
tarPath string
|
||||
tarPath string
|
||||
removeTarPathOnClose bool // Remove temp file on close if true
|
||||
// The following data is only available after ensureCachedDataIsPresent() succeeds
|
||||
tarManifest *ManifestItem // nil if not available yet.
|
||||
configBytes []byte
|
||||
configDigest digest.Digest
|
||||
orderedDiffIDList []diffID
|
||||
knownLayers map[diffID]*layerInfo
|
||||
orderedDiffIDList []digest.Digest
|
||||
knownLayers map[digest.Digest]*layerInfo
|
||||
// Other state
|
||||
generatedManifest []byte // Private cache for GetManifest(), nil if not set yet.
|
||||
}
|
||||
@@ -35,14 +38,75 @@ type layerInfo struct {
|
||||
size int64
|
||||
}
|
||||
|
||||
// NewSource returns a tarfile.Source for the specified path.
|
||||
func NewSource(path string) *Source {
|
||||
// TODO: We could add support for multiple images in a single archive, so
|
||||
// that people could use docker-archive:opensuse.tar:opensuse:leap as
|
||||
// the source of an image.
|
||||
return &Source{
|
||||
tarPath: path,
|
||||
// TODO: We could add support for multiple images in a single archive, so
|
||||
// that people could use docker-archive:opensuse.tar:opensuse:leap as
|
||||
// the source of an image.
|
||||
// To do for both the NewSourceFromFile and NewSourceFromStream functions
|
||||
|
||||
// NewSourceFromFile returns a tarfile.Source for the specified path.
|
||||
func NewSourceFromFile(path string) (*Source, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening file %q", path)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// If the file is already not compressed we can just return the file itself
|
||||
// as a source. Otherwise we pass the stream to NewSourceFromStream.
|
||||
stream, isCompressed, err := compression.AutoDecompress(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error detecting compression for file %q", path)
|
||||
}
|
||||
defer stream.Close()
|
||||
if !isCompressed {
|
||||
return &Source{
|
||||
tarPath: path,
|
||||
}, nil
|
||||
}
|
||||
return NewSourceFromStream(stream)
|
||||
}
|
||||
|
||||
// NewSourceFromStream returns a tarfile.Source for the specified inputStream,
|
||||
// which can be either compressed or uncompressed. The caller can close the
|
||||
// inputStream immediately after NewSourceFromFile returns.
|
||||
func NewSourceFromStream(inputStream io.Reader) (*Source, error) {
|
||||
// FIXME: use SystemContext here.
|
||||
// Save inputStream to a temporary file
|
||||
tarCopyFile, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-tar")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating temporary file")
|
||||
}
|
||||
defer tarCopyFile.Close()
|
||||
|
||||
succeeded := false
|
||||
defer func() {
|
||||
if !succeeded {
|
||||
os.Remove(tarCopyFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
// In order to be compatible with docker-load, we need to support
|
||||
// auto-decompression (it's also a nice quality-of-life thing to avoid
|
||||
// giving users really confusing "invalid tar header" errors).
|
||||
uncompressedStream, _, err := compression.AutoDecompress(inputStream)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error auto-decompressing input")
|
||||
}
|
||||
defer uncompressedStream.Close()
|
||||
|
||||
// Copy the plain archive to the temporary file.
|
||||
//
|
||||
// TODO: This can take quite some time, and should ideally be cancellable
|
||||
// using a context.Context.
|
||||
if _, err := io.Copy(tarCopyFile, uncompressedStream); err != nil {
|
||||
return nil, errors.Wrapf(err, "error copying contents to temporary file %q", tarCopyFile.Name())
|
||||
}
|
||||
succeeded = true
|
||||
|
||||
return &Source{
|
||||
tarPath: tarCopyFile.Name(),
|
||||
removeTarPathOnClose: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// tarReadCloser is a way to close the backing file of a tar.Reader when the user no longer needs the tar component.
|
||||
@@ -121,13 +185,13 @@ func findTarComponent(inputFile io.Reader, path string) (*tar.Reader, *tar.Heade
|
||||
}
|
||||
|
||||
// readTarComponent returns full contents of componentPath.
|
||||
func (s *Source) readTarComponent(path string) ([]byte, error) {
|
||||
func (s *Source) readTarComponent(path string, limit int) ([]byte, error) {
|
||||
file, err := s.openTarComponent(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error loading tar component %s", path)
|
||||
}
|
||||
defer file.Close()
|
||||
bytes, err := ioutil.ReadAll(file)
|
||||
bytes, err := iolimits.ReadAtMost(file, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -150,13 +214,12 @@ func (s *Source) ensureCachedDataIsPresent() error {
|
||||
if len(tarManifest) != 1 {
|
||||
return errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(tarManifest))
|
||||
}
|
||||
|
||||
// Read and parse config.
|
||||
configBytes, err := s.readTarComponent(tarManifest[0].Config)
|
||||
configBytes, err := s.readTarComponent(tarManifest[0].Config, iolimits.MaxConfigBodySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var parsedConfig image // Most fields ommitted, we only care about layer DiffIDs.
|
||||
var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
|
||||
if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
|
||||
return errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
|
||||
}
|
||||
@@ -178,7 +241,7 @@ func (s *Source) ensureCachedDataIsPresent() error {
|
||||
// loadTarManifest loads and decodes the manifest.json.
|
||||
func (s *Source) loadTarManifest() ([]ManifestItem, error) {
|
||||
// FIXME? Do we need to deal with the legacy format?
|
||||
bytes, err := s.readTarComponent(manifestFileName)
|
||||
bytes, err := s.readTarComponent(manifestFileName, iolimits.MaxTarFileManifestSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -189,17 +252,25 @@ func (s *Source) loadTarManifest() ([]ManifestItem, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized Source, if any.
|
||||
func (s *Source) Close() error {
|
||||
if s.removeTarPathOnClose {
|
||||
return os.Remove(s.tarPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTarManifest loads and decodes the manifest.json
|
||||
func (s *Source) LoadTarManifest() ([]ManifestItem, error) {
|
||||
return s.loadTarManifest()
|
||||
}
|
||||
|
||||
func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image) (map[diffID]*layerInfo, error) {
|
||||
func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manifest.Schema2Image) (map[digest.Digest]*layerInfo, error) {
|
||||
// Collect layer data available in manifest and config.
|
||||
if len(tarManifest.Layers) != len(parsedConfig.RootFS.DiffIDs) {
|
||||
return nil, errors.Errorf("Inconsistent layer count: %d in manifest, %d in config", len(tarManifest.Layers), len(parsedConfig.RootFS.DiffIDs))
|
||||
}
|
||||
knownLayers := map[diffID]*layerInfo{}
|
||||
knownLayers := map[digest.Digest]*layerInfo{}
|
||||
unknownLayerSizes := map[string]*layerInfo{} // Points into knownLayers, a "to do list" of items with unknown sizes.
|
||||
for i, diffID := range parsedConfig.RootFS.DiffIDs {
|
||||
if _, ok := knownLayers[diffID]; ok {
|
||||
@@ -236,7 +307,25 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image
|
||||
return nil, err
|
||||
}
|
||||
if li, ok := unknownLayerSizes[h.Name]; ok {
|
||||
li.size = h.Size
|
||||
// Since GetBlob will decompress layers that are compressed we need
|
||||
// to do the decompression here as well, otherwise we will
|
||||
// incorrectly report the size. Pretty critical, since tools like
|
||||
// umoci always compress layer blobs. Obviously we only bother with
|
||||
// the slower method of checking if it's compressed.
|
||||
uncompressedStream, isCompressed, err := compression.AutoDecompress(t)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error auto-decompressing %s to determine its size", h.Name)
|
||||
}
|
||||
defer uncompressedStream.Close()
|
||||
|
||||
uncompressedSize := h.Size
|
||||
if isCompressed {
|
||||
uncompressedSize, err = io.Copy(ioutil.Discard, uncompressedStream)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error reading %s to find its size", h.Name)
|
||||
}
|
||||
}
|
||||
li.size = uncompressedSize
|
||||
delete(unknownLayerSizes, h.Name)
|
||||
}
|
||||
}
|
||||
@@ -251,32 +340,32 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *Source) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
func (s *Source) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
// How did we even get here? GetManifest(nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
// How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
if s.generatedManifest == nil {
|
||||
if err := s.ensureCachedDataIsPresent(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
m := schema2Manifest{
|
||||
m := manifest.Schema2{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2Schema2MediaType,
|
||||
Config: distributionDescriptor{
|
||||
ConfigDescriptor: manifest.Schema2Descriptor{
|
||||
MediaType: manifest.DockerV2Schema2ConfigMediaType,
|
||||
Size: int64(len(s.configBytes)),
|
||||
Digest: s.configDigest,
|
||||
},
|
||||
Layers: []distributionDescriptor{},
|
||||
LayersDescriptors: []manifest.Schema2Descriptor{},
|
||||
}
|
||||
for _, diffID := range s.orderedDiffIDList {
|
||||
li, ok := s.knownLayers[diffID]
|
||||
if !ok {
|
||||
return nil, "", errors.Errorf("Internal inconsistency: Information about layer %s missing", diffID)
|
||||
}
|
||||
m.Layers = append(m.Layers, distributionDescriptor{
|
||||
Digest: digest.Digest(diffID), // diffID is a digest of the uncompressed tarball
|
||||
m.LayersDescriptors = append(m.LayersDescriptors, manifest.Schema2Descriptor{
|
||||
Digest: diffID, // diffID is a digest of the uncompressed tarball
|
||||
MediaType: manifest.DockerV2Schema2LayerMediaType,
|
||||
Size: li.size,
|
||||
})
|
||||
@@ -290,20 +379,26 @@ func (s *Source) GetManifest(instanceDigest *digest.Digest) ([]byte, string, err
|
||||
return s.generatedManifest, manifest.DockerV2Schema2MediaType, nil
|
||||
}
|
||||
|
||||
type readCloseWrapper struct {
|
||||
// uncompressedReadCloser is an io.ReadCloser that closes both the uncompressed stream and the underlying input.
|
||||
type uncompressedReadCloser struct {
|
||||
io.Reader
|
||||
closeFunc func() error
|
||||
underlyingCloser func() error
|
||||
uncompressedCloser func() error
|
||||
}
|
||||
|
||||
func (r readCloseWrapper) Close() error {
|
||||
if r.closeFunc != nil {
|
||||
return r.closeFunc()
|
||||
func (r uncompressedReadCloser) Close() error {
|
||||
var res error
|
||||
if err := r.uncompressedCloser(); err != nil {
|
||||
res = err
|
||||
}
|
||||
return nil
|
||||
if err := r.underlyingCloser(); err != nil && res == nil {
|
||||
res = err
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
func (s *Source) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if err := s.ensureCachedDataIsPresent(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -312,11 +407,17 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(s.configBytes)), int64(len(s.configBytes)), nil
|
||||
}
|
||||
|
||||
if li, ok := s.knownLayers[diffID(info.Digest)]; ok { // diffID is a digest of the uncompressed tarball,
|
||||
stream, err := s.openTarComponent(li.path)
|
||||
if li, ok := s.knownLayers[info.Digest]; ok { // diffID is a digest of the uncompressed tarball,
|
||||
underlyingStream, err := s.openTarComponent(li.path)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
closeUnderlyingStream := true
|
||||
defer func() {
|
||||
if closeUnderlyingStream {
|
||||
underlyingStream.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// In order to handle the fact that digests != diffIDs (and thus that a
|
||||
// caller which is trying to verify the blob will run into problems),
|
||||
@@ -330,22 +431,17 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
// be verifing a "digest" which is not the actual layer's digest (but
|
||||
// is instead the DiffID).
|
||||
|
||||
decompressFunc, reader, err := compression.DetectCompression(stream)
|
||||
uncompressedStream, _, err := compression.AutoDecompress(underlyingStream)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "Detecting compression in blob %s", info.Digest)
|
||||
return nil, 0, errors.Wrapf(err, "Error auto-decompressing blob %s", info.Digest)
|
||||
}
|
||||
|
||||
if decompressFunc != nil {
|
||||
reader, err = decompressFunc(reader)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "Decompressing blob %s stream", info.Digest)
|
||||
}
|
||||
}
|
||||
|
||||
newStream := readCloseWrapper{
|
||||
Reader: reader,
|
||||
closeFunc: stream.Close,
|
||||
newStream := uncompressedReadCloser{
|
||||
Reader: uncompressedStream,
|
||||
underlyingCloser: underlyingStream.Close,
|
||||
uncompressedCloser: uncompressedStream.Close,
|
||||
}
|
||||
closeUnderlyingStream = false
|
||||
|
||||
return newStream, li.size, nil
|
||||
}
|
||||
@@ -359,7 +455,7 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
// (e.g. if the source never returns manifest lists).
|
||||
func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if instanceDigest != nil {
|
||||
// How did we even get here? GetManifest(nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
// How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
return [][]byte{}, nil
|
||||
|
||||
48
vendor/github.com/containers/image/docker/tarfile/types.go
generated
vendored
48
vendor/github.com/containers/image/docker/tarfile/types.go
generated
vendored
@@ -1,16 +1,19 @@
|
||||
package tarfile
|
||||
|
||||
import "github.com/opencontainers/go-digest"
|
||||
import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Various data structures.
|
||||
|
||||
// Based on github.com/docker/docker/image/tarexport/tarexport.go
|
||||
const (
|
||||
manifestFileName = "manifest.json"
|
||||
// legacyLayerFileName = "layer.tar"
|
||||
// legacyConfigFileName = "json"
|
||||
// legacyVersionFileName = "VERSION"
|
||||
// legacyRepositoriesFileName = "repositories"
|
||||
manifestFileName = "manifest.json"
|
||||
legacyLayerFileName = "layer.tar"
|
||||
legacyConfigFileName = "json"
|
||||
legacyVersionFileName = "VERSION"
|
||||
legacyRepositoriesFileName = "repositories"
|
||||
)
|
||||
|
||||
// ManifestItem is an element of the array stored in the top-level manifest.json file.
|
||||
@@ -18,37 +21,8 @@ type ManifestItem struct {
|
||||
Config string
|
||||
RepoTags []string
|
||||
Layers []string
|
||||
Parent imageID `json:",omitempty"`
|
||||
LayerSources map[diffID]distributionDescriptor `json:",omitempty"`
|
||||
Parent imageID `json:",omitempty"`
|
||||
LayerSources map[digest.Digest]manifest.Schema2Descriptor `json:",omitempty"`
|
||||
}
|
||||
|
||||
type imageID string
|
||||
type diffID digest.Digest
|
||||
|
||||
// Based on github.com/docker/distribution/blobs.go
|
||||
type distributionDescriptor struct {
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Digest digest.Digest `json:"digest,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
|
||||
// Based on github.com/docker/distribution/manifest/schema2/manifest.go
|
||||
// FIXME: We are repeating this all over the place; make a public copy?
|
||||
type schema2Manifest struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Config distributionDescriptor `json:"config"`
|
||||
Layers []distributionDescriptor `json:"layers"`
|
||||
}
|
||||
|
||||
// Based on github.com/docker/docker/image/image.go
|
||||
// MOST CONTENT OMITTED AS UNNECESSARY
|
||||
type image struct {
|
||||
RootFS *rootFS `json:"rootfs,omitempty"`
|
||||
}
|
||||
|
||||
type rootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []diffID `json:"diff_ids,omitempty"`
|
||||
}
|
||||
|
||||
27
vendor/github.com/containers/image/image/docker_list.go
generated
vendored
27
vendor/github.com/containers/image/image/docker_list.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
@@ -22,7 +23,7 @@ type platformSpec struct {
|
||||
|
||||
// A manifestDescriptor references a platform-specific manifest.
|
||||
type manifestDescriptor struct {
|
||||
descriptor
|
||||
manifest.Schema2Descriptor
|
||||
Platform platformSpec `json:"platform"`
|
||||
}
|
||||
|
||||
@@ -34,14 +35,14 @@ type manifestList struct {
|
||||
|
||||
// chooseDigestFromManifestList parses blob as a schema2 manifest list,
|
||||
// and returns the digest of the image appropriate for the current environment.
|
||||
func chooseDigestFromManifestList(ctx *types.SystemContext, blob []byte) (digest.Digest, error) {
|
||||
func chooseDigestFromManifestList(sys *types.SystemContext, blob []byte) (digest.Digest, error) {
|
||||
wantedArch := runtime.GOARCH
|
||||
if ctx != nil && ctx.ArchitectureChoice != "" {
|
||||
wantedArch = ctx.ArchitectureChoice
|
||||
if sys != nil && sys.ArchitectureChoice != "" {
|
||||
wantedArch = sys.ArchitectureChoice
|
||||
}
|
||||
wantedOS := runtime.GOOS
|
||||
if ctx != nil && ctx.OSChoice != "" {
|
||||
wantedOS = ctx.OSChoice
|
||||
if sys != nil && sys.OSChoice != "" {
|
||||
wantedOS = sys.OSChoice
|
||||
}
|
||||
|
||||
list := manifestList{}
|
||||
@@ -56,12 +57,12 @@ func chooseDigestFromManifestList(ctx *types.SystemContext, blob []byte) (digest
|
||||
return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS)
|
||||
}
|
||||
|
||||
func manifestSchema2FromManifestList(ctx *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
targetManifestDigest, err := chooseDigestFromManifestList(ctx, manblob)
|
||||
func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
targetManifestDigest, err := chooseDigestFromManifestList(sys, manblob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manblob, mt, err := src.GetManifest(&targetManifestDigest)
|
||||
manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,20 +75,20 @@ func manifestSchema2FromManifestList(ctx *types.SystemContext, src types.ImageSo
|
||||
return nil, errors.Errorf("Manifest image does not match selected manifest digest %s", targetManifestDigest)
|
||||
}
|
||||
|
||||
return manifestInstanceFromBlob(ctx, src, manblob, mt)
|
||||
return manifestInstanceFromBlob(ctx, sys, src, manblob, mt)
|
||||
}
|
||||
|
||||
// ChooseManifestInstanceFromManifestList returns a digest of a manifest appropriate
|
||||
// for the current system from the manifest available from src.
|
||||
func ChooseManifestInstanceFromManifestList(ctx *types.SystemContext, src types.UnparsedImage) (digest.Digest, error) {
|
||||
func ChooseManifestInstanceFromManifestList(ctx context.Context, sys *types.SystemContext, src types.UnparsedImage) (digest.Digest, error) {
|
||||
// For now this only handles manifest.DockerV2ListMediaType; we can generalize it later,
|
||||
// probably along with manifest list editing.
|
||||
blob, mt, err := src.Manifest()
|
||||
blob, mt, err := src.Manifest(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if mt != manifest.DockerV2ListMediaType {
|
||||
return "", fmt.Errorf("Internal error: Trying to select an image from a non-manifest-list manifest type %s", mt)
|
||||
}
|
||||
return chooseDigestFromManifestList(ctx, blob)
|
||||
return chooseDigestFromManifestList(sys, blob)
|
||||
}
|
||||
|
||||
299
vendor/github.com/containers/image/image/docker_schema1.go
generated
vendored
299
vendor/github.com/containers/image/image/docker_schema1.go
generated
vendored
@@ -1,10 +1,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
@@ -14,87 +11,29 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||
)
|
||||
|
||||
type fsLayersSchema1 struct {
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
type historySchema1 struct {
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
// historySchema1 is a string containing this. It is similar to v1Image but not the same, in particular note the ThrowAway field.
|
||||
type v1Compatibility struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig struct {
|
||||
Cmd []string
|
||||
} `json:"container_config,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
ThrowAway bool `json:"throwaway,omitempty"`
|
||||
}
|
||||
|
||||
type manifestSchema1 struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Architecture string `json:"architecture"`
|
||||
FSLayers []fsLayersSchema1 `json:"fsLayers"`
|
||||
History []historySchema1 `json:"history"`
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
m *manifest.Schema1
|
||||
}
|
||||
|
||||
func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) {
|
||||
mschema1 := &manifestSchema1{}
|
||||
if err := json.Unmarshal(manifest, mschema1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mschema1.SchemaVersion != 1 {
|
||||
return nil, errors.Errorf("unsupported schema version %d", mschema1.SchemaVersion)
|
||||
}
|
||||
if len(mschema1.FSLayers) != len(mschema1.History) {
|
||||
return nil, errors.New("length of history not equal to number of layers")
|
||||
}
|
||||
if len(mschema1.FSLayers) == 0 {
|
||||
return nil, errors.New("no FSLayers in manifest")
|
||||
}
|
||||
|
||||
if err := fixManifestLayers(mschema1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mschema1, nil
|
||||
}
|
||||
|
||||
// manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data.
|
||||
func manifestSchema1FromComponents(ref reference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest {
|
||||
var name, tag string
|
||||
if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them.
|
||||
name = reference.Path(ref)
|
||||
if tagged, ok := ref.(reference.NamedTagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
return &manifestSchema1{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: architecture,
|
||||
FSLayers: fsLayers,
|
||||
History: history,
|
||||
SchemaVersion: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) serialize() ([]byte, error) {
|
||||
// docker/distribution requires a signature even if the incoming data uses the nominally unsigned DockerV2Schema1MediaType.
|
||||
unsigned, err := json.Marshal(*m)
|
||||
func manifestSchema1FromManifest(manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.Schema1FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return manifest.AddDummyV2S1Signature(unsigned)
|
||||
return &manifestSchema1{m: m}, nil
|
||||
}
|
||||
|
||||
// manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data.
|
||||
func manifestSchema1FromComponents(ref reference.Named, fsLayers []manifest.Schema1FSLayers, history []manifest.Schema1History, architecture string) (genericManifest, error) {
|
||||
m, err := manifest.Schema1FromComponents(ref, fsLayers, history, architecture)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &manifestSchema1{m: m}, nil
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) serialize() ([]byte, error) {
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) manifestMIMEType() string {
|
||||
@@ -104,35 +43,31 @@ func (m *manifestSchema1) manifestMIMEType() string {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestSchema1) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
// The result is cached; it is OK to call this however often you need.
|
||||
func (m *manifestSchema1) ConfigBlob() ([]byte, error) {
|
||||
func (m *manifestSchema1) ConfigBlob(context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
// layers in the resulting configuration isn't guaranteed to be returned to due how
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
func (m *manifestSchema1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
func (m *manifestSchema1) OCIConfig(ctx context.Context) (*imgspecv1.Image, error) {
|
||||
v2s2, err := m.convertToManifestSchema2(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v2s2.OCIConfig()
|
||||
return v2s2.OCIConfig(ctx)
|
||||
}
|
||||
|
||||
// LayerInfos returns a list of BlobInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestSchema1) LayerInfos() []types.BlobInfo {
|
||||
layers := make([]types.BlobInfo, len(m.FSLayers))
|
||||
for i, layer := range m.FSLayers { // NOTE: This includes empty layers (where m.History.V1Compatibility->ThrowAway)
|
||||
layers[(len(m.FSLayers)-1)-i] = types.BlobInfo{Digest: layer.BlobSum, Size: -1}
|
||||
}
|
||||
return layers
|
||||
return manifestLayerInfosToBlobInfos(m.m.LayerInfos())
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -153,56 +88,36 @@ func (m *manifestSchema1) EmbeddedDockerReferenceConflicts(ref reference.Named)
|
||||
} else {
|
||||
tag = ""
|
||||
}
|
||||
return m.Name != name || m.Tag != tag
|
||||
return m.m.Name != name || m.m.Tag != tag
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: m.Tag,
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *manifestSchema1) Inspect(context.Context) (*types.ImageInspectInfo, error) {
|
||||
return m.m.Inspect(nil)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
|
||||
// (most importantly it forces us to download the full layers even if they are already present at the destination).
|
||||
func (m *manifestSchema1) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdateOptions) bool {
|
||||
return options.ManifestMIMEType == manifest.DockerV2Schema2MediaType
|
||||
return (options.ManifestMIMEType == manifest.DockerV2Schema2MediaType || options.ManifestMIMEType == imgspecv1.MediaTypeImageManifest)
|
||||
}
|
||||
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m
|
||||
func (m *manifestSchema1) UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := manifestSchema1{m: manifest.Schema1Clone(m.m)}
|
||||
if options.LayerInfos != nil {
|
||||
// Our LayerInfos includes empty layers (where m.History.V1Compatibility->ThrowAway), so expect them to be included here as well.
|
||||
if len(copy.FSLayers) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.FSLayers), len(options.LayerInfos))
|
||||
}
|
||||
for i, info := range options.LayerInfos {
|
||||
// (docker push) sets up m.History.V1Compatibility->{Id,Parent} based on values of info.Digest,
|
||||
// but (docker pull) ignores them in favor of computing DiffIDs from uncompressed data, except verifying the child->parent links and uniqueness.
|
||||
// So, we don't bother recomputing the IDs in m.History.V1Compatibility.
|
||||
copy.FSLayers[(len(options.LayerInfos)-1)-i].BlobSum = info.Digest
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.EmbeddedDockerReference != nil {
|
||||
copy.Name = reference.Path(options.EmbeddedDockerReference)
|
||||
copy.m.Name = reference.Path(options.EmbeddedDockerReference)
|
||||
if tagged, isTagged := options.EmbeddedDockerReference.(reference.NamedTagged); isTagged {
|
||||
copy.Tag = tagged.Tag()
|
||||
copy.m.Tag = tagged.Tag()
|
||||
} else {
|
||||
copy.Tag = ""
|
||||
copy.m.Tag = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +138,7 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m2.UpdatedImage(types.ManifestUpdateOptions{
|
||||
return m2.UpdatedImage(ctx, types.ManifestUpdateOptions{
|
||||
ManifestMIMEType: imgspecv1.MediaTypeImageManifest,
|
||||
InformationOnly: options.InformationOnly,
|
||||
})
|
||||
@@ -234,103 +149,29 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
// fixManifestLayers, after validating the supplied manifest
|
||||
// (to use correctly-formatted IDs, and to not have non-consecutive ID collisions in manifest.History),
|
||||
// modifies manifest to only have one entry for each layer ID in manifest.History (deleting the older duplicates,
|
||||
// both from manifest.History and manifest.FSLayers).
|
||||
// Note that even after this succeeds, manifest.FSLayers may contain duplicate entries
|
||||
// (for Dockerfile operations which change the configuration but not the filesystem).
|
||||
func fixManifestLayers(manifest *manifestSchema1) error {
|
||||
type imageV1 struct {
|
||||
ID string
|
||||
Parent string
|
||||
}
|
||||
// Per the specification, we can assume that len(manifest.FSLayers) == len(manifest.History)
|
||||
imgs := make([]*imageV1, len(manifest.FSLayers))
|
||||
for i := range manifest.FSLayers {
|
||||
img := &imageV1{}
|
||||
|
||||
if err := json.Unmarshal([]byte(manifest.History[i].V1Compatibility), img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgs[i] = img
|
||||
if err := validateV1ID(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if imgs[len(imgs)-1].Parent != "" {
|
||||
return errors.New("Invalid parent ID in the base layer of the image")
|
||||
}
|
||||
// check general duplicates to error instead of a deadlock
|
||||
idmap := make(map[string]struct{})
|
||||
var lastID string
|
||||
for _, img := range imgs {
|
||||
// skip IDs that appear after each other, we handle those later
|
||||
if _, exists := idmap[img.ID]; img.ID != lastID && exists {
|
||||
return errors.Errorf("ID %+v appears multiple times in manifest", img.ID)
|
||||
}
|
||||
lastID = img.ID
|
||||
idmap[lastID] = struct{}{}
|
||||
}
|
||||
// backwards loop so that we keep the remaining indexes after removing items
|
||||
for i := len(imgs) - 2; i >= 0; i-- {
|
||||
if imgs[i].ID == imgs[i+1].ID { // repeated ID. remove and continue
|
||||
manifest.FSLayers = append(manifest.FSLayers[:i], manifest.FSLayers[i+1:]...)
|
||||
manifest.History = append(manifest.History[:i], manifest.History[i+1:]...)
|
||||
} else if imgs[i].Parent != imgs[i+1].ID {
|
||||
return errors.Errorf("Invalid parent ID. Expected %v, got %v", imgs[i+1].ID, imgs[i].Parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateV1ID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return errors.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Based on github.com/docker/docker/distribution/pull_v2.go
|
||||
func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.BlobInfo, layerDiffIDs []digest.Digest) (genericManifest, error) {
|
||||
if len(m.History) == 0 {
|
||||
// What would this even mean?! Anyhow, the rest of the code depends on fsLayers[0] and history[0] existing.
|
||||
if len(m.m.ExtractedV1Compatibility) == 0 {
|
||||
// What would this even mean?! Anyhow, the rest of the code depends on FSLayers[0] and ExtractedV1Compatibility[0] existing.
|
||||
return nil, errors.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema2MediaType)
|
||||
}
|
||||
if len(m.History) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.History), len(m.FSLayers))
|
||||
if len(m.m.ExtractedV1Compatibility) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.m.ExtractedV1Compatibility), len(m.m.FSLayers))
|
||||
}
|
||||
if uploadedLayerInfos != nil && len(uploadedLayerInfos) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.FSLayers))
|
||||
if uploadedLayerInfos != nil && len(uploadedLayerInfos) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.m.FSLayers))
|
||||
}
|
||||
if layerDiffIDs != nil && len(layerDiffIDs) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.FSLayers))
|
||||
if layerDiffIDs != nil && len(layerDiffIDs) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.m.FSLayers))
|
||||
}
|
||||
|
||||
rootFS := rootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []digest.Digest{},
|
||||
BaseLayer: "",
|
||||
}
|
||||
var layers []descriptor
|
||||
history := make([]imageHistory, len(m.History))
|
||||
for v1Index := len(m.History) - 1; v1Index >= 0; v1Index-- {
|
||||
v2Index := (len(m.History) - 1) - v1Index
|
||||
// Build a list of the diffIDs for the non-empty layers.
|
||||
diffIDs := []digest.Digest{}
|
||||
var layers []manifest.Schema2Descriptor
|
||||
for v1Index := len(m.m.ExtractedV1Compatibility) - 1; v1Index >= 0; v1Index-- {
|
||||
v2Index := (len(m.m.ExtractedV1Compatibility) - 1) - v1Index
|
||||
|
||||
var v1compat v1Compatibility
|
||||
if err := json.Unmarshal([]byte(m.History[v1Index].V1Compatibility), &v1compat); err != nil {
|
||||
return nil, errors.Wrapf(err, "Error decoding history entry %d", v1Index)
|
||||
}
|
||||
history[v2Index] = imageHistory{
|
||||
Created: v1compat.Created,
|
||||
Author: v1compat.Author,
|
||||
CreatedBy: strings.Join(v1compat.ContainerConfig.Cmd, " "),
|
||||
Comment: v1compat.Comment,
|
||||
EmptyLayer: v1compat.ThrowAway,
|
||||
}
|
||||
|
||||
if !v1compat.ThrowAway {
|
||||
if !m.m.ExtractedV1Compatibility[v1Index].ThrowAway {
|
||||
var size int64
|
||||
if uploadedLayerInfos != nil {
|
||||
size = uploadedLayerInfos[v2Index].Size
|
||||
@@ -339,19 +180,19 @@ func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.Bl
|
||||
if layerDiffIDs != nil {
|
||||
d = layerDiffIDs[v2Index]
|
||||
}
|
||||
layers = append(layers, descriptor{
|
||||
layers = append(layers, manifest.Schema2Descriptor{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Size: size,
|
||||
Digest: m.FSLayers[v1Index].BlobSum,
|
||||
Digest: m.m.FSLayers[v1Index].BlobSum,
|
||||
})
|
||||
rootFS.DiffIDs = append(rootFS.DiffIDs, d)
|
||||
diffIDs = append(diffIDs, d)
|
||||
}
|
||||
}
|
||||
configJSON, err := configJSONFromV1Config([]byte(m.History[0].V1Compatibility), rootFS, history)
|
||||
configJSON, err := m.m.ToSchema2Config(diffIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configDescriptor := descriptor{
|
||||
configDescriptor := manifest.Schema2Descriptor{
|
||||
MediaType: "application/vnd.docker.container.image.v1+json",
|
||||
Size: int64(len(configJSON)),
|
||||
Digest: digest.FromBytes(configJSON),
|
||||
@@ -359,33 +200,3 @@ func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.Bl
|
||||
|
||||
return manifestSchema2FromComponents(configDescriptor, nil, configJSON, layers), nil
|
||||
}
|
||||
|
||||
func configJSONFromV1Config(v1ConfigJSON []byte, rootFS rootFS, history []imageHistory) ([]byte, error) {
|
||||
// github.com/docker/docker/image/v1/imagev1.go:MakeConfigFromV1Config unmarshals and re-marshals the input if docker_version is < 1.8.3 to remove blank fields;
|
||||
// we don't do that here. FIXME? Should we? AFAICT it would only affect the digest value of the schema2 manifest, and we don't particularly need that to be
|
||||
// a consistently reproducible value.
|
||||
|
||||
// Preserve everything we don't specifically know about.
|
||||
// (This must be a *json.RawMessage, even though *[]byte is fairly redundant, because only *RawMessage implements json.Marshaler.)
|
||||
rawContents := map[string]*json.RawMessage{}
|
||||
if err := json.Unmarshal(v1ConfigJSON, &rawContents); err != nil { // We have already unmarshaled it before, using a more detailed schema?!
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(rawContents, "id")
|
||||
delete(rawContents, "parent")
|
||||
delete(rawContents, "Size")
|
||||
delete(rawContents, "parent_id")
|
||||
delete(rawContents, "layer_id")
|
||||
delete(rawContents, "throwaway")
|
||||
|
||||
updates := map[string]interface{}{"rootfs": rootFS, "history": history}
|
||||
for field, value := range updates {
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawContents[field] = (*json.RawMessage)(&encoded)
|
||||
}
|
||||
return json.Marshal(rawContents)
|
||||
}
|
||||
|
||||
201
vendor/github.com/containers/image/image/docker_schema2.go
generated
vendored
201
vendor/github.com/containers/image/image/docker_schema2.go
generated
vendored
@@ -2,13 +2,14 @@ package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -17,73 +18,63 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// gzippedEmptyLayer is a gzip-compressed version of an empty tar file (1024 NULL bytes)
|
||||
// GzippedEmptyLayer is a gzip-compressed version of an empty tar file (1024 NULL bytes)
|
||||
// This comes from github.com/docker/distribution/manifest/schema1/config_builder.go; there is
|
||||
// a non-zero embedded timestamp; we could zero that, but that would just waste storage space
|
||||
// in registries, so let’s use the same values.
|
||||
var gzippedEmptyLayer = []byte{
|
||||
var GzippedEmptyLayer = []byte{
|
||||
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
|
||||
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
|
||||
}
|
||||
|
||||
// gzippedEmptyLayerDigest is a digest of gzippedEmptyLayer
|
||||
const gzippedEmptyLayerDigest = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
type descriptor struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Size int64 `json:"size"`
|
||||
Digest digest.Digest `json:"digest"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
// GzippedEmptyLayerDigest is a digest of GzippedEmptyLayer
|
||||
const GzippedEmptyLayerDigest = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
type manifestSchema2 struct {
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType"`
|
||||
ConfigDescriptor descriptor `json:"config"`
|
||||
LayersDescriptors []descriptor `json:"layers"`
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
m *manifest.Schema2
|
||||
}
|
||||
|
||||
func manifestSchema2FromManifest(src types.ImageSource, manifest []byte) (genericManifest, error) {
|
||||
v2s2 := manifestSchema2{src: src}
|
||||
if err := json.Unmarshal(manifest, &v2s2); err != nil {
|
||||
func manifestSchema2FromManifest(src types.ImageSource, manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.Schema2FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v2s2, nil
|
||||
return &manifestSchema2{
|
||||
src: src,
|
||||
m: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// manifestSchema2FromComponents builds a new manifestSchema2 from the supplied data:
|
||||
func manifestSchema2FromComponents(config descriptor, src types.ImageSource, configBlob []byte, layers []descriptor) genericManifest {
|
||||
func manifestSchema2FromComponents(config manifest.Schema2Descriptor, src types.ImageSource, configBlob []byte, layers []manifest.Schema2Descriptor) genericManifest {
|
||||
return &manifestSchema2{
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2Schema2MediaType,
|
||||
ConfigDescriptor: config,
|
||||
LayersDescriptors: layers,
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
m: manifest.Schema2FromComponents(config, layers),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) manifestMIMEType() string {
|
||||
return m.MediaType
|
||||
return m.m.MediaType
|
||||
}
|
||||
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestSchema2) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{Digest: m.ConfigDescriptor.Digest, Size: m.ConfigDescriptor.Size}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
// layers in the resulting configuration isn't guaranteed to be returned to due how
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
func (m *manifestSchema2) OCIConfig() (*imgspecv1.Image, error) {
|
||||
configBlob, err := m.ConfigBlob()
|
||||
func (m *manifestSchema2) OCIConfig(ctx context.Context) (*imgspecv1.Image, error) {
|
||||
configBlob, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,27 +90,23 @@ func (m *manifestSchema2) OCIConfig() (*imgspecv1.Image, error) {
|
||||
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
// The result is cached; it is OK to call this however often you need.
|
||||
func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
|
||||
func (m *manifestSchema2) ConfigBlob(ctx context.Context) ([]byte, error) {
|
||||
if m.configBlob == nil {
|
||||
if m.src == nil {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestSchema2")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(types.BlobInfo{
|
||||
Digest: m.ConfigDescriptor.Digest,
|
||||
Size: m.ConfigDescriptor.Size,
|
||||
URLs: m.ConfigDescriptor.URLs,
|
||||
})
|
||||
stream, _, err := m.src.GetBlob(ctx, manifest.BlobInfoFromSchema2Descriptor(m.m.ConfigDescriptor))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Close()
|
||||
blob, err := ioutil.ReadAll(stream)
|
||||
blob, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.ConfigDescriptor.Digest)
|
||||
if computedDigest != m.m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.ConfigDescriptor.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
@@ -130,15 +117,7 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestSchema2) LayerInfos() []types.BlobInfo {
|
||||
blobs := []types.BlobInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, types.BlobInfo{
|
||||
Digest: layer.Digest,
|
||||
Size: layer.Size,
|
||||
URLs: layer.URLs,
|
||||
})
|
||||
}
|
||||
return blobs
|
||||
return manifestLayerInfosToBlobInfos(m.m.LayerInfos())
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -148,25 +127,20 @@ func (m *manifestSchema2) EmbeddedDockerReferenceConflicts(ref reference.Named)
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *manifestSchema2) Inspect(ctx context.Context) (*types.ImageInspectInfo, error) {
|
||||
getter := func(info types.BlobInfo) ([]byte, error) {
|
||||
if info.Digest != m.ConfigInfo().Digest {
|
||||
// Shouldn't ever happen
|
||||
return nil, errors.New("asked for a different config blob")
|
||||
}
|
||||
config, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
return m.m.Inspect(getter)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
@@ -178,18 +152,15 @@ func (m *manifestSchema2) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUp
|
||||
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
func (m *manifestSchema2) UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := manifestSchema2{ // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
src: m.src,
|
||||
configBlob: m.configBlob,
|
||||
m: manifest.Schema2Clone(m.m),
|
||||
}
|
||||
if options.LayerInfos != nil {
|
||||
if len(copy.LayersDescriptors) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.LayersDescriptors), len(options.LayerInfos))
|
||||
}
|
||||
copy.LayersDescriptors = make([]descriptor, len(options.LayerInfos))
|
||||
for i, info := range options.LayerInfos {
|
||||
copy.LayersDescriptors[i].MediaType = m.LayersDescriptors[i].MediaType
|
||||
copy.LayersDescriptors[i].Digest = info.Digest
|
||||
copy.LayersDescriptors[i].Size = info.Size
|
||||
copy.LayersDescriptors[i].URLs = info.URLs
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1 to schema2, but we really don't care.
|
||||
@@ -197,9 +168,9 @@ func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
switch options.ManifestMIMEType {
|
||||
case "": // No conversion, OK
|
||||
case manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema1MediaType:
|
||||
return copy.convertToManifestSchema1(options.InformationOnly.Destination)
|
||||
return copy.convertToManifestSchema1(ctx, options.InformationOnly.Destination)
|
||||
case imgspecv1.MediaTypeImageManifest:
|
||||
return copy.convertToManifestOCI1()
|
||||
return copy.convertToManifestOCI1(ctx)
|
||||
default:
|
||||
return nil, errors.Errorf("Conversion of image manifest from %s to %s is not implemented", manifest.DockerV2Schema2MediaType, options.ManifestMIMEType)
|
||||
}
|
||||
@@ -207,8 +178,17 @@ func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) convertToManifestOCI1() (types.Image, error) {
|
||||
configOCI, err := m.OCIConfig()
|
||||
func oci1DescriptorFromSchema2Descriptor(d manifest.Schema2Descriptor) imgspecv1.Descriptor {
|
||||
return imgspecv1.Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Size: d.Size,
|
||||
Digest: d.Digest,
|
||||
URLs: d.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) convertToManifestOCI1(ctx context.Context) (types.Image, error) {
|
||||
configOCI, err := m.OCIConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,18 +197,16 @@ func (m *manifestSchema2) convertToManifestOCI1() (types.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := descriptorOCI1{
|
||||
descriptor: descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Size: int64(len(configOCIBytes)),
|
||||
Digest: digest.FromBytes(configOCIBytes),
|
||||
},
|
||||
config := imgspecv1.Descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Size: int64(len(configOCIBytes)),
|
||||
Digest: digest.FromBytes(configOCIBytes),
|
||||
}
|
||||
|
||||
layers := make([]descriptorOCI1, len(m.LayersDescriptors))
|
||||
layers := make([]imgspecv1.Descriptor, len(m.m.LayersDescriptors))
|
||||
for idx := range layers {
|
||||
layers[idx] = descriptorOCI1{descriptor: m.LayersDescriptors[idx]}
|
||||
if m.LayersDescriptors[idx].MediaType == manifest.DockerV2Schema2ForeignLayerMediaType {
|
||||
layers[idx] = oci1DescriptorFromSchema2Descriptor(m.m.LayersDescriptors[idx])
|
||||
if m.m.LayersDescriptors[idx].MediaType == manifest.DockerV2Schema2ForeignLayerMediaType {
|
||||
layers[idx].MediaType = imgspecv1.MediaTypeImageLayerNonDistributable
|
||||
} else {
|
||||
// we assume layers are gzip'ed because docker v2s2 only deals with
|
||||
@@ -242,19 +220,19 @@ func (m *manifestSchema2) convertToManifestOCI1() (types.Image, error) {
|
||||
}
|
||||
|
||||
// Based on docker/distribution/manifest/schema1/config_builder.go
|
||||
func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination) (types.Image, error) {
|
||||
configBytes, err := m.ConfigBlob()
|
||||
func (m *manifestSchema2) convertToManifestSchema1(ctx context.Context, dest types.ImageDestination) (types.Image, error) {
|
||||
configBytes, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageConfig := &image{}
|
||||
imageConfig := &manifest.Schema2Image{}
|
||||
if err := json.Unmarshal(configBytes, imageConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build fsLayers and History, discarding all configs. We will patch the top-level config in later.
|
||||
fsLayers := make([]fsLayersSchema1, len(imageConfig.History))
|
||||
history := make([]historySchema1, len(imageConfig.History))
|
||||
fsLayers := make([]manifest.Schema1FSLayers, len(imageConfig.History))
|
||||
history := make([]manifest.Schema1History, len(imageConfig.History))
|
||||
nonemptyLayerIndex := 0
|
||||
var parentV1ID string // Set in the loop
|
||||
v1ID := ""
|
||||
@@ -271,21 +249,21 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
if historyEntry.EmptyLayer {
|
||||
if !haveGzippedEmptyLayer {
|
||||
logrus.Debugf("Uploading empty layer during conversion to schema 1")
|
||||
info, err := dest.PutBlob(bytes.NewReader(gzippedEmptyLayer), types.BlobInfo{Digest: gzippedEmptyLayerDigest, Size: int64(len(gzippedEmptyLayer))})
|
||||
info, err := dest.PutBlob(ctx, bytes.NewReader(GzippedEmptyLayer), types.BlobInfo{Digest: GzippedEmptyLayerDigest, Size: int64(len(GzippedEmptyLayer))}, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error uploading empty layer")
|
||||
}
|
||||
if info.Digest != gzippedEmptyLayerDigest {
|
||||
return nil, errors.Errorf("Internal error: Uploaded empty layer has digest %#v instead of %s", info.Digest, gzippedEmptyLayerDigest)
|
||||
if info.Digest != GzippedEmptyLayerDigest {
|
||||
return nil, errors.Errorf("Internal error: Uploaded empty layer has digest %#v instead of %s", info.Digest, GzippedEmptyLayerDigest)
|
||||
}
|
||||
haveGzippedEmptyLayer = true
|
||||
}
|
||||
blobDigest = gzippedEmptyLayerDigest
|
||||
blobDigest = GzippedEmptyLayerDigest
|
||||
} else {
|
||||
if nonemptyLayerIndex >= len(m.LayersDescriptors) {
|
||||
return nil, errors.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.LayersDescriptors))
|
||||
if nonemptyLayerIndex >= len(m.m.LayersDescriptors) {
|
||||
return nil, errors.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.m.LayersDescriptors))
|
||||
}
|
||||
blobDigest = m.LayersDescriptors[nonemptyLayerIndex].Digest
|
||||
blobDigest = m.m.LayersDescriptors[nonemptyLayerIndex].Digest
|
||||
nonemptyLayerIndex++
|
||||
}
|
||||
|
||||
@@ -296,7 +274,7 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
}
|
||||
v1ID = v
|
||||
|
||||
fakeImage := v1Compatibility{
|
||||
fakeImage := manifest.Schema1V1Compatibility{
|
||||
ID: v1ID,
|
||||
Parent: parentV1ID,
|
||||
Comment: historyEntry.Comment,
|
||||
@@ -310,8 +288,8 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
return nil, errors.Errorf("Internal error: Error creating v1compatibility for %#v", fakeImage)
|
||||
}
|
||||
|
||||
fsLayers[v1Index] = fsLayersSchema1{BlobSum: blobDigest}
|
||||
history[v1Index] = historySchema1{V1Compatibility: string(v1CompatibilityBytes)}
|
||||
fsLayers[v1Index] = manifest.Schema1FSLayers{BlobSum: blobDigest}
|
||||
history[v1Index] = manifest.Schema1History{V1Compatibility: string(v1CompatibilityBytes)}
|
||||
// Note that parentV1ID of the top layer is preserved when exiting this loop
|
||||
}
|
||||
|
||||
@@ -326,7 +304,10 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
}
|
||||
history[0].V1Compatibility = string(v1Config)
|
||||
|
||||
m1 := manifestSchema1FromComponents(dest.Reference().DockerReference(), fsLayers, history, imageConfig.Architecture)
|
||||
m1, err := manifestSchema1FromComponents(dest.Reference().DockerReference(), fsLayers, history, imageConfig.Architecture)
|
||||
if err != nil {
|
||||
return nil, err // This should never happen, we should have created all the components correctly.
|
||||
}
|
||||
return memoryImageFromManifest(m1), nil
|
||||
}
|
||||
|
||||
|
||||
94
vendor/github.com/containers/image/image/manifest.go
generated
vendored
94
vendor/github.com/containers/image/image/manifest.go
generated
vendored
@@ -1,57 +1,15 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"time"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/strslice"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Cmd strslice.StrSlice
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
type v1Image struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig *config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
// Config is the configuration of the container received from the client
|
||||
Config *config `json:"config,omitempty"`
|
||||
// Architecture is the hardware that the image is build and runs on
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
// OS is the operating system used to build and run the image
|
||||
OS string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
type image struct {
|
||||
v1Image
|
||||
History []imageHistory `json:"history,omitempty"`
|
||||
RootFS *rootFS `json:"rootfs,omitempty"`
|
||||
}
|
||||
|
||||
type imageHistory struct {
|
||||
Created time.Time `json:"created"`
|
||||
Author string `json:"author,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||
}
|
||||
|
||||
type rootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []digest.Digest `json:"diff_ids,omitempty"`
|
||||
BaseLayer string `json:"base_layer,omitempty"`
|
||||
}
|
||||
|
||||
// genericManifest is an interface for parsing, modifying image manifests and related data.
|
||||
// Note that the public methods are intended to be a subset of types.Image
|
||||
// so that embedding a genericManifest into structs works.
|
||||
@@ -64,11 +22,11 @@ type genericManifest interface {
|
||||
ConfigInfo() types.BlobInfo
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
// The result is cached; it is OK to call this however often you need.
|
||||
ConfigBlob() ([]byte, error)
|
||||
ConfigBlob(context.Context) ([]byte, error)
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
// layers in the resulting configuration isn't guaranteed to be returned to due how
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
OCIConfig() (*imgspecv1.Image, error)
|
||||
OCIConfig(context.Context) (*imgspecv1.Image, error)
|
||||
// LayerInfos returns a list of BlobInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
@@ -77,55 +35,39 @@ type genericManifest interface {
|
||||
// It returns false if the manifest does not embed a Docker reference.
|
||||
// (This embedding unfortunately happens for Docker schema1, please do not add support for this in any new formats.)
|
||||
EmbeddedDockerReferenceConflicts(ref reference.Named) bool
|
||||
imageInspectInfo() (*types.ImageInspectInfo, error) // To be called by inspectManifest
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
Inspect(context.Context) (*types.ImageInspectInfo, error)
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
// This is a horribly specific interface, but computing InformationOnly.LayerDiffIDs can be very expensive to compute
|
||||
// (most importantly it forces us to download the full layers even if they are already present at the destination).
|
||||
UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdateOptions) bool
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error)
|
||||
UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error)
|
||||
}
|
||||
|
||||
// manifestInstanceFromBlob returns a genericManifest implementation for (manblob, mt) in src.
|
||||
// If manblob is a manifest list, it implicitly chooses an appropriate image from the list.
|
||||
func manifestInstanceFromBlob(ctx *types.SystemContext, src types.ImageSource, manblob []byte, mt string) (genericManifest, error) {
|
||||
switch mt {
|
||||
// "application/json" is a valid v2s1 value per https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md .
|
||||
// This works for now, when nothing else seems to return "application/json"; if that were not true, the mapping/detection might
|
||||
// need to happen within the ImageSource.
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, "application/json":
|
||||
func manifestInstanceFromBlob(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte, mt string) (genericManifest, error) {
|
||||
switch manifest.NormalizedMIMEType(mt) {
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType:
|
||||
return manifestSchema1FromManifest(manblob)
|
||||
case imgspecv1.MediaTypeImageManifest:
|
||||
return manifestOCI1FromManifest(src, manblob)
|
||||
case manifest.DockerV2Schema2MediaType:
|
||||
return manifestSchema2FromManifest(src, manblob)
|
||||
case manifest.DockerV2ListMediaType:
|
||||
return manifestSchema2FromManifestList(ctx, src, manblob)
|
||||
default:
|
||||
// If it's not a recognized manifest media type, or we have failed determining the type, we'll try one last time
|
||||
// to deserialize using v2s1 as per https://github.com/docker/distribution/blob/master/manifests.go#L108
|
||||
// and https://github.com/docker/distribution/blob/master/manifest/schema1/manifest.go#L50
|
||||
//
|
||||
// Crane registries can also return "text/plain", or pretty much anything else depending on a file extension “recognized” in the tag.
|
||||
// This makes no real sense, but it happens
|
||||
// because requests for manifests are
|
||||
// redirected to a content distribution
|
||||
// network which is configured that way. See https://bugzilla.redhat.com/show_bug.cgi?id=1389442
|
||||
return manifestSchema1FromManifest(manblob)
|
||||
return manifestSchema2FromManifestList(ctx, sys, src, manblob)
|
||||
default: // Note that this may not be reachable, manifest.NormalizedMIMEType has a default for unknown values.
|
||||
return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt)
|
||||
}
|
||||
}
|
||||
|
||||
// inspectManifest is an implementation of types.Image.Inspect
|
||||
func inspectManifest(m genericManifest) (*types.ImageInspectInfo, error) {
|
||||
info, err := m.imageInspectInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers := m.LayerInfos()
|
||||
info.Layers = make([]string, len(layers))
|
||||
// manifestLayerInfosToBlobInfos extracts a []types.BlobInfo from a []manifest.LayerInfo.
|
||||
func manifestLayerInfosToBlobInfos(layers []manifest.LayerInfo) []types.BlobInfo {
|
||||
blobs := make([]types.BlobInfo, len(layers))
|
||||
for i, layer := range layers {
|
||||
info.Layers[i] = layer.Digest.String()
|
||||
blobs[i] = layer.BlobInfo
|
||||
}
|
||||
return info, nil
|
||||
return blobs
|
||||
}
|
||||
|
||||
10
vendor/github.com/containers/image/image/memory.go
generated
vendored
10
vendor/github.com/containers/image/image/memory.go
generated
vendored
@@ -39,7 +39,7 @@ func (i *memoryImage) Size() (int64, error) {
|
||||
}
|
||||
|
||||
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
|
||||
func (i *memoryImage) Manifest() ([]byte, string, error) {
|
||||
func (i *memoryImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
if i.serializedManifest == nil {
|
||||
m, err := i.genericManifest.serialize()
|
||||
if err != nil {
|
||||
@@ -57,7 +57,9 @@ func (i *memoryImage) Signatures(ctx context.Context) ([][]byte, error) {
|
||||
return nil, errors.New("Internal error: Image.Signatures() is not supported for images modified in memory")
|
||||
}
|
||||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (i *memoryImage) Inspect() (*types.ImageInspectInfo, error) {
|
||||
return inspectManifest(i.genericManifest)
|
||||
// LayerInfosForCopy returns an updated set of layer blob information which may not match the manifest.
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (i *memoryImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
142
vendor/github.com/containers/image/image/oci.go
generated
vendored
142
vendor/github.com/containers/image/image/oci.go
generated
vendored
@@ -1,10 +1,11 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -12,41 +13,34 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type descriptorOCI1 struct {
|
||||
descriptor
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type manifestOCI1 struct {
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
ConfigDescriptor descriptorOCI1 `json:"config"`
|
||||
LayersDescriptors []descriptorOCI1 `json:"layers"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of m.Config.
|
||||
m *manifest.OCI1
|
||||
}
|
||||
|
||||
func manifestOCI1FromManifest(src types.ImageSource, manifest []byte) (genericManifest, error) {
|
||||
oci := manifestOCI1{src: src}
|
||||
if err := json.Unmarshal(manifest, &oci); err != nil {
|
||||
func manifestOCI1FromManifest(src types.ImageSource, manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.OCI1FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oci, nil
|
||||
return &manifestOCI1{
|
||||
src: src,
|
||||
m: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// manifestOCI1FromComponents builds a new manifestOCI1 from the supplied data:
|
||||
func manifestOCI1FromComponents(config descriptorOCI1, src types.ImageSource, configBlob []byte, layers []descriptorOCI1) genericManifest {
|
||||
func manifestOCI1FromComponents(config imgspecv1.Descriptor, src types.ImageSource, configBlob []byte, layers []imgspecv1.Descriptor) genericManifest {
|
||||
return &manifestOCI1{
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
SchemaVersion: 2,
|
||||
ConfigDescriptor: config,
|
||||
LayersDescriptors: layers,
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
m: manifest.OCI1FromComponents(config, layers),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) manifestMIMEType() string {
|
||||
@@ -56,32 +50,28 @@ func (m *manifestOCI1) manifestMIMEType() string {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestOCI1) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{Digest: m.ConfigDescriptor.Digest, Size: m.ConfigDescriptor.Size, Annotations: m.ConfigDescriptor.Annotations}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
// The result is cached; it is OK to call this however often you need.
|
||||
func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
|
||||
func (m *manifestOCI1) ConfigBlob(ctx context.Context) ([]byte, error) {
|
||||
if m.configBlob == nil {
|
||||
if m.src == nil {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestOCI1")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(types.BlobInfo{
|
||||
Digest: m.ConfigDescriptor.Digest,
|
||||
Size: m.ConfigDescriptor.Size,
|
||||
URLs: m.ConfigDescriptor.URLs,
|
||||
})
|
||||
stream, _, err := m.src.GetBlob(ctx, manifest.BlobInfoFromOCI1Descriptor(m.m.Config))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Close()
|
||||
blob, err := ioutil.ReadAll(stream)
|
||||
blob, err := iolimits.ReadAtMost(stream, iolimits.MaxConfigBodySize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.ConfigDescriptor.Digest)
|
||||
if computedDigest != m.m.Config.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.Config.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
@@ -91,8 +81,8 @@ func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
// layers in the resulting configuration isn't guaranteed to be returned to due how
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
func (m *manifestOCI1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
cb, err := m.ConfigBlob()
|
||||
func (m *manifestOCI1) OCIConfig(ctx context.Context) (*imgspecv1.Image, error) {
|
||||
cb, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -107,11 +97,7 @@ func (m *manifestOCI1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
|
||||
blobs := []types.BlobInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs, MediaType: layer.MediaType})
|
||||
}
|
||||
return blobs
|
||||
return manifestLayerInfosToBlobInfos(m.m.LayerInfos())
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -121,25 +107,20 @@ func (m *manifestOCI1) EmbeddedDockerReferenceConflicts(ref reference.Named) boo
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *manifestOCI1) Inspect(ctx context.Context) (*types.ImageInspectInfo, error) {
|
||||
getter := func(info types.BlobInfo) ([]byte, error) {
|
||||
if info.Digest != m.ConfigInfo().Digest {
|
||||
// Shouldn't ever happen
|
||||
return nil, errors.New("asked for a different config blob")
|
||||
}
|
||||
config, err := m.ConfigBlob(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
return m.m.Inspect(getter)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
@@ -151,25 +132,31 @@ func (m *manifestOCI1) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdat
|
||||
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
func (m *manifestOCI1) UpdatedImage(ctx context.Context, options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := manifestOCI1{ // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
src: m.src,
|
||||
configBlob: m.configBlob,
|
||||
m: manifest.OCI1Clone(m.m),
|
||||
}
|
||||
if options.LayerInfos != nil {
|
||||
if len(copy.LayersDescriptors) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.LayersDescriptors), len(options.LayerInfos))
|
||||
}
|
||||
copy.LayersDescriptors = make([]descriptorOCI1, len(options.LayerInfos))
|
||||
for i, info := range options.LayerInfos {
|
||||
copy.LayersDescriptors[i].MediaType = m.LayersDescriptors[i].MediaType
|
||||
copy.LayersDescriptors[i].Digest = info.Digest
|
||||
copy.LayersDescriptors[i].Size = info.Size
|
||||
copy.LayersDescriptors[i].Annotations = info.Annotations
|
||||
copy.LayersDescriptors[i].URLs = info.URLs
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1, but we really don't care.
|
||||
|
||||
switch options.ManifestMIMEType {
|
||||
case "": // No conversion, OK
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType:
|
||||
// We can't directly convert to V1, but we can transitively convert via a V2 image
|
||||
m2, err := copy.convertToManifestSchema2()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m2.UpdatedImage(ctx, types.ManifestUpdateOptions{
|
||||
ManifestMIMEType: options.ManifestMIMEType,
|
||||
InformationOnly: options.InformationOnly,
|
||||
})
|
||||
case manifest.DockerV2Schema2MediaType:
|
||||
return copy.convertToManifestSchema2()
|
||||
default:
|
||||
@@ -179,17 +166,26 @@ func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
func schema2DescriptorFromOCI1Descriptor(d imgspecv1.Descriptor) manifest.Schema2Descriptor {
|
||||
return manifest.Schema2Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Size: d.Size,
|
||||
Digest: d.Digest,
|
||||
URLs: d.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) convertToManifestSchema2() (types.Image, error) {
|
||||
// Create a copy of the descriptor.
|
||||
config := m.ConfigDescriptor.descriptor
|
||||
config := schema2DescriptorFromOCI1Descriptor(m.m.Config)
|
||||
|
||||
// The only difference between OCI and DockerSchema2 is the mediatypes. The
|
||||
// media type of the manifest is handled by manifestSchema2FromComponents.
|
||||
config.MediaType = manifest.DockerV2Schema2ConfigMediaType
|
||||
|
||||
layers := make([]descriptor, len(m.LayersDescriptors))
|
||||
layers := make([]manifest.Schema2Descriptor, len(m.m.Layers))
|
||||
for idx := range layers {
|
||||
layers[idx] = m.LayersDescriptors[idx].descriptor
|
||||
layers[idx] = schema2DescriptorFromOCI1Descriptor(m.m.Layers[idx])
|
||||
layers[idx].MediaType = manifest.DockerV2Schema2LayerMediaType
|
||||
}
|
||||
|
||||
|
||||
17
vendor/github.com/containers/image/image/sourced.go
generated
vendored
17
vendor/github.com/containers/image/image/sourced.go
generated
vendored
@@ -4,6 +4,7 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
@@ -28,8 +29,8 @@ type imageCloser struct {
|
||||
//
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage instead of calling this function.
|
||||
func FromSource(ctx *types.SystemContext, src types.ImageSource) (types.ImageCloser, error) {
|
||||
img, err := FromUnparsedImage(ctx, UnparsedInstance(src, nil))
|
||||
func FromSource(ctx context.Context, sys *types.SystemContext, src types.ImageSource) (types.ImageCloser, error) {
|
||||
img, err := FromUnparsedImage(ctx, sys, UnparsedInstance(src, nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,18 +64,18 @@ type sourcedImage struct {
|
||||
// but other methods transparently return data from an appropriate single image.
|
||||
//
|
||||
// The Image must not be used after the underlying ImageSource is Close()d.
|
||||
func FromUnparsedImage(ctx *types.SystemContext, unparsed *UnparsedImage) (types.Image, error) {
|
||||
func FromUnparsedImage(ctx context.Context, sys *types.SystemContext, unparsed *UnparsedImage) (types.Image, error) {
|
||||
// Note that the input parameter above is specifically *image.UnparsedImage, not types.UnparsedImage:
|
||||
// we want to be able to use unparsed.src. We could make that an explicit interface, but, well,
|
||||
// this is the only UnparsedImage implementation around, anyway.
|
||||
|
||||
// NOTE: It is essential for signature verification that all parsing done in this object happens on the same manifest which is returned by unparsed.Manifest().
|
||||
manifestBlob, manifestMIMEType, err := unparsed.Manifest()
|
||||
manifestBlob, manifestMIMEType, err := unparsed.Manifest(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedManifest, err := manifestInstanceFromBlob(ctx, unparsed.src, manifestBlob, manifestMIMEType)
|
||||
parsedManifest, err := manifestInstanceFromBlob(ctx, sys, unparsed.src, manifestBlob, manifestMIMEType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,10 +94,10 @@ func (i *sourcedImage) Size() (int64, error) {
|
||||
}
|
||||
|
||||
// Manifest overrides the UnparsedImage.Manifest to always use the fields which we have already fetched.
|
||||
func (i *sourcedImage) Manifest() ([]byte, string, error) {
|
||||
func (i *sourcedImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
return i.manifestBlob, i.manifestMIMEType, nil
|
||||
}
|
||||
|
||||
func (i *sourcedImage) Inspect() (*types.ImageInspectInfo, error) {
|
||||
return inspectManifest(i.genericManifest)
|
||||
func (i *sourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return i.UnparsedImage.src.LayerInfosForCopy(ctx)
|
||||
}
|
||||
|
||||
4
vendor/github.com/containers/image/image/unparsed.go
generated
vendored
4
vendor/github.com/containers/image/image/unparsed.go
generated
vendored
@@ -41,9 +41,9 @@ func (i *UnparsedImage) Reference() types.ImageReference {
|
||||
}
|
||||
|
||||
// Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need.
|
||||
func (i *UnparsedImage) Manifest() ([]byte, string, error) {
|
||||
func (i *UnparsedImage) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
if i.cachedManifest == nil {
|
||||
m, mt, err := i.src.GetManifest(i.instanceDigest)
|
||||
m, mt, err := i.src.GetManifest(ctx, i.instanceDigest)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
60
vendor/github.com/containers/image/internal/iolimits/iolimits.go
generated
vendored
Normal file
60
vendor/github.com/containers/image/internal/iolimits/iolimits.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package iolimits
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// All constants below are intended to be used as limits for `ReadAtMost`. The
|
||||
// immediate use-case for limiting the size of in-memory copied data is to
|
||||
// protect against OOM DOS attacks as described inCVE-2020-1702. Instead of
|
||||
// copying data until running out of memory, we error out after hitting the
|
||||
// specified limit.
|
||||
const (
|
||||
// megaByte denotes one megabyte and is intended to be used as a limit in
|
||||
// `ReadAtMost`.
|
||||
megaByte = 1 << 20
|
||||
// MaxManifestBodySize is the maximum allowed size of a manifest. The limit
|
||||
// of 4 MB aligns with the one of a Docker registry:
|
||||
// https://github.com/docker/distribution/blob/a8371794149d1d95f1e846744b05c87f2f825e5a/registry/handlers/manifests.go#L30
|
||||
MaxManifestBodySize = 4 * megaByte
|
||||
// MaxAuthTokenBodySize is the maximum allowed size of an auth token.
|
||||
// The limit of 1 MB is considered to be greatly sufficient.
|
||||
MaxAuthTokenBodySize = megaByte
|
||||
// MaxSignatureListBodySize is the maximum allowed size of a signature list.
|
||||
// The limit of 4 MB is considered to be greatly sufficient.
|
||||
MaxSignatureListBodySize = 4 * megaByte
|
||||
// MaxSignatureBodySize is the maximum allowed size of a signature.
|
||||
// The limit of 4 MB is considered to be greatly sufficient.
|
||||
MaxSignatureBodySize = 4 * megaByte
|
||||
// MaxErrorBodySize is the maximum allowed size of an error-response body.
|
||||
// The limit of 1 MB is considered to be greatly sufficient.
|
||||
MaxErrorBodySize = megaByte
|
||||
// MaxConfigBodySize is the maximum allowed size of a config blob.
|
||||
// The limit of 4 MB is considered to be greatly sufficient.
|
||||
MaxConfigBodySize = 4 * megaByte
|
||||
// MaxOpenShiftStatusBody is the maximum allowed size of an OpenShift status body.
|
||||
// The limit of 4 MB is considered to be greatly sufficient.
|
||||
MaxOpenShiftStatusBody = 4 * megaByte
|
||||
// MaxTarFileManifestSize is the maximum allowed size of a (docker save)-like manifest (which may contain multiple images)
|
||||
// The limit of 1 MB is considered to be greatly sufficient.
|
||||
MaxTarFileManifestSize = megaByte
|
||||
)
|
||||
|
||||
// ReadAtMost reads from reader and errors out if the specified limit (in bytes) is exceeded.
|
||||
func ReadAtMost(reader io.Reader, limit int) ([]byte, error) {
|
||||
limitedReader := io.LimitReader(reader, int64(limit+1))
|
||||
|
||||
res, err := ioutil.ReadAll(limitedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res) > limit {
|
||||
return nil, errors.Errorf("exceeded maximum allowed size of %d bytes", limit)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
29
vendor/github.com/containers/image/internal/tmpdir/tmpdir.go
generated
vendored
Normal file
29
vendor/github.com/containers/image/internal/tmpdir/tmpdir.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package tmpdir
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// unixTempDirForBigFiles is the directory path to store big files on non Windows systems.
|
||||
// You can override this at build time with
|
||||
// -ldflags '-X github.com/containers/image/internal/tmpdir.unixTempDirForBigFiles=$your_path'
|
||||
var unixTempDirForBigFiles = builtinUnixTempDirForBigFiles
|
||||
|
||||
// builtinUnixTempDirForBigFiles is the directory path to store big files.
|
||||
// Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
|
||||
// DO NOT change this, instead see unixTempDirForBigFiles above.
|
||||
const builtinUnixTempDirForBigFiles = "/var/tmp"
|
||||
|
||||
// TemporaryDirectoryForBigFiles returns a directory for temporary (big) files.
|
||||
// On non Windows systems it avoids the use of os.TempDir(), because the default temporary directory usually falls under /tmp
|
||||
// which on systemd based systems could be the unsuitable tmpfs filesystem.
|
||||
func TemporaryDirectoryForBigFiles() string {
|
||||
var temporaryDirectoryForBigFiles string
|
||||
if runtime.GOOS == "windows" {
|
||||
temporaryDirectoryForBigFiles = os.TempDir()
|
||||
} else {
|
||||
temporaryDirectoryForBigFiles = unixTempDirForBigFiles
|
||||
}
|
||||
return temporaryDirectoryForBigFiles
|
||||
}
|
||||
315
vendor/github.com/containers/image/manifest/docker_schema1.go
generated
vendored
Normal file
315
vendor/github.com/containers/image/manifest/docker_schema1.go
generated
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Schema1FSLayers is an entry of the "fsLayers" array in docker/distribution schema 1.
|
||||
type Schema1FSLayers struct {
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
// Schema1History is an entry of the "history" array in docker/distribution schema 1.
|
||||
type Schema1History struct {
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
// Schema1 is a manifest in docker/distribution schema 1.
|
||||
type Schema1 struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Architecture string `json:"architecture"`
|
||||
FSLayers []Schema1FSLayers `json:"fsLayers"`
|
||||
History []Schema1History `json:"history"` // Keep this in sync with ExtractedV1Compatibility!
|
||||
ExtractedV1Compatibility []Schema1V1Compatibility `json:"-"` // Keep this in sync with History! Does not contain the full config (Schema2V1Image)
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
}
|
||||
|
||||
type schema1V1CompatibilityContainerConfig struct {
|
||||
Cmd []string
|
||||
}
|
||||
|
||||
// Schema1V1Compatibility is a v1Compatibility in docker/distribution schema 1.
|
||||
type Schema1V1Compatibility struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig schema1V1CompatibilityContainerConfig `json:"container_config,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
ThrowAway bool `json:"throwaway,omitempty"`
|
||||
}
|
||||
|
||||
// Schema1FromManifest creates a Schema1 manifest instance from a manifest blob.
|
||||
// (NOTE: The instance is not necessary a literal representation of the original blob,
|
||||
// layers with duplicate IDs are eliminated.)
|
||||
func Schema1FromManifest(manifest []byte) (*Schema1, error) {
|
||||
s1 := Schema1{}
|
||||
if err := json.Unmarshal(manifest, &s1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s1.SchemaVersion != 1 {
|
||||
return nil, errors.Errorf("unsupported schema version %d", s1.SchemaVersion)
|
||||
}
|
||||
if err := s1.initialize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s1.fixManifestLayers(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s1, nil
|
||||
}
|
||||
|
||||
// Schema1FromComponents creates an Schema1 manifest instance from the supplied data.
|
||||
func Schema1FromComponents(ref reference.Named, fsLayers []Schema1FSLayers, history []Schema1History, architecture string) (*Schema1, error) {
|
||||
var name, tag string
|
||||
if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them.
|
||||
name = reference.Path(ref)
|
||||
if tagged, ok := ref.(reference.NamedTagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
s1 := Schema1{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: architecture,
|
||||
FSLayers: fsLayers,
|
||||
History: history,
|
||||
SchemaVersion: 1,
|
||||
}
|
||||
if err := s1.initialize(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s1, nil
|
||||
}
|
||||
|
||||
// Schema1Clone creates a copy of the supplied Schema1 manifest.
|
||||
func Schema1Clone(src *Schema1) *Schema1 {
|
||||
copy := *src
|
||||
return ©
|
||||
}
|
||||
|
||||
// initialize initializes ExtractedV1Compatibility and verifies invariants, so that the rest of this code can assume a minimally healthy manifest.
|
||||
func (m *Schema1) initialize() error {
|
||||
if len(m.FSLayers) != len(m.History) {
|
||||
return errors.New("length of history not equal to number of layers")
|
||||
}
|
||||
if len(m.FSLayers) == 0 {
|
||||
return errors.New("no FSLayers in manifest")
|
||||
}
|
||||
m.ExtractedV1Compatibility = make([]Schema1V1Compatibility, len(m.History))
|
||||
for i, h := range m.History {
|
||||
if err := json.Unmarshal([]byte(h.V1Compatibility), &m.ExtractedV1Compatibility[i]); err != nil {
|
||||
return errors.Wrapf(err, "Error parsing v2s1 history entry %d", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
func (m *Schema1) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{}
|
||||
}
|
||||
|
||||
// LayerInfos returns a list of LayerInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *Schema1) LayerInfos() []LayerInfo {
|
||||
layers := make([]LayerInfo, len(m.FSLayers))
|
||||
for i, layer := range m.FSLayers { // NOTE: This includes empty layers (where m.History.V1Compatibility->ThrowAway)
|
||||
layers[(len(m.FSLayers)-1)-i] = LayerInfo{
|
||||
BlobInfo: types.BlobInfo{Digest: layer.BlobSum, Size: -1},
|
||||
EmptyLayer: m.ExtractedV1Compatibility[i].ThrowAway,
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
|
||||
func (m *Schema1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
// Our LayerInfos includes empty layers (where m.ExtractedV1Compatibility[].ThrowAway), so expect them to be included here as well.
|
||||
if len(m.FSLayers) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.FSLayers), len(layerInfos))
|
||||
}
|
||||
m.FSLayers = make([]Schema1FSLayers, len(layerInfos))
|
||||
for i, info := range layerInfos {
|
||||
// (docker push) sets up m.ExtractedV1Compatibility[].{Id,Parent} based on values of info.Digest,
|
||||
// but (docker pull) ignores them in favor of computing DiffIDs from uncompressed data, except verifying the child->parent links and uniqueness.
|
||||
// So, we don't bother recomputing the IDs in m.History.V1Compatibility.
|
||||
m.FSLayers[(len(layerInfos)-1)-i].BlobSum = info.Digest
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize returns the manifest in a blob format.
|
||||
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
||||
func (m *Schema1) Serialize() ([]byte, error) {
|
||||
// docker/distribution requires a signature even if the incoming data uses the nominally unsigned DockerV2Schema1MediaType.
|
||||
unsigned, err := json.Marshal(*m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AddDummyV2S1Signature(unsigned)
|
||||
}
|
||||
|
||||
// fixManifestLayers, after validating the supplied manifest
|
||||
// (to use correctly-formatted IDs, and to not have non-consecutive ID collisions in m.History),
|
||||
// modifies manifest to only have one entry for each layer ID in m.History (deleting the older duplicates,
|
||||
// both from m.History and m.FSLayers).
|
||||
// Note that even after this succeeds, m.FSLayers may contain duplicate entries
|
||||
// (for Dockerfile operations which change the configuration but not the filesystem).
|
||||
func (m *Schema1) fixManifestLayers() error {
|
||||
// m.initialize() has verified that len(m.FSLayers) == len(m.History)
|
||||
for _, compat := range m.ExtractedV1Compatibility {
|
||||
if err := validateV1ID(compat.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if m.ExtractedV1Compatibility[len(m.ExtractedV1Compatibility)-1].Parent != "" {
|
||||
return errors.New("Invalid parent ID in the base layer of the image")
|
||||
}
|
||||
// check general duplicates to error instead of a deadlock
|
||||
idmap := make(map[string]struct{})
|
||||
var lastID string
|
||||
for _, img := range m.ExtractedV1Compatibility {
|
||||
// skip IDs that appear after each other, we handle those later
|
||||
if _, exists := idmap[img.ID]; img.ID != lastID && exists {
|
||||
return errors.Errorf("ID %+v appears multiple times in manifest", img.ID)
|
||||
}
|
||||
lastID = img.ID
|
||||
idmap[lastID] = struct{}{}
|
||||
}
|
||||
// backwards loop so that we keep the remaining indexes after removing items
|
||||
for i := len(m.ExtractedV1Compatibility) - 2; i >= 0; i-- {
|
||||
if m.ExtractedV1Compatibility[i].ID == m.ExtractedV1Compatibility[i+1].ID { // repeated ID. remove and continue
|
||||
m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...)
|
||||
m.History = append(m.History[:i], m.History[i+1:]...)
|
||||
m.ExtractedV1Compatibility = append(m.ExtractedV1Compatibility[:i], m.ExtractedV1Compatibility[i+1:]...)
|
||||
} else if m.ExtractedV1Compatibility[i].Parent != m.ExtractedV1Compatibility[i+1].ID {
|
||||
return errors.Errorf("Invalid parent ID. Expected %v, got %v", m.ExtractedV1Compatibility[i+1].ID, m.ExtractedV1Compatibility[i].Parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||
|
||||
func validateV1ID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return errors.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *Schema1) Inspect(_ func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
|
||||
s1 := &Schema2V1Image{}
|
||||
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), s1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: m.Tag,
|
||||
Created: &s1.Created,
|
||||
DockerVersion: s1.DockerVersion,
|
||||
Architecture: s1.Architecture,
|
||||
Os: s1.OS,
|
||||
Layers: layerInfosToStrings(m.LayerInfos()),
|
||||
}
|
||||
if s1.Config != nil {
|
||||
i.Labels = s1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ToSchema2Config builds a schema2-style configuration blob using the supplied diffIDs.
|
||||
func (m *Schema1) ToSchema2Config(diffIDs []digest.Digest) ([]byte, error) {
|
||||
// Convert the schema 1 compat info into a schema 2 config, constructing some of the fields
|
||||
// that aren't directly comparable using info from the manifest.
|
||||
if len(m.History) == 0 {
|
||||
return nil, errors.New("image has no layers")
|
||||
}
|
||||
s1 := Schema2V1Image{}
|
||||
config := []byte(m.History[0].V1Compatibility)
|
||||
err := json.Unmarshal(config, &s1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error decoding configuration")
|
||||
}
|
||||
// Images created with versions prior to 1.8.3 require us to re-encode the encoded object,
|
||||
// adding some fields that aren't "omitempty".
|
||||
if s1.DockerVersion != "" && versions.LessThan(s1.DockerVersion, "1.8.3") {
|
||||
config, err = json.Marshal(&s1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error re-encoding compat image config %#v", s1)
|
||||
}
|
||||
}
|
||||
// Build the history.
|
||||
convertedHistory := []Schema2History{}
|
||||
for _, compat := range m.ExtractedV1Compatibility {
|
||||
hitem := Schema2History{
|
||||
Created: compat.Created,
|
||||
CreatedBy: strings.Join(compat.ContainerConfig.Cmd, " "),
|
||||
Author: compat.Author,
|
||||
Comment: compat.Comment,
|
||||
EmptyLayer: compat.ThrowAway,
|
||||
}
|
||||
convertedHistory = append([]Schema2History{hitem}, convertedHistory...)
|
||||
}
|
||||
// Build the rootfs information. We need the decompressed sums that we've been
|
||||
// calculating to fill in the DiffIDs. It's expected (but not enforced by us)
|
||||
// that the number of diffIDs corresponds to the number of non-EmptyLayer
|
||||
// entries in the history.
|
||||
rootFS := &Schema2RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: diffIDs,
|
||||
}
|
||||
// And now for some raw manipulation.
|
||||
raw := make(map[string]*json.RawMessage)
|
||||
err = json.Unmarshal(config, &raw)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error re-decoding compat image config %#v: %v", s1)
|
||||
}
|
||||
// Drop some fields.
|
||||
delete(raw, "id")
|
||||
delete(raw, "parent")
|
||||
delete(raw, "parent_id")
|
||||
delete(raw, "layer_id")
|
||||
delete(raw, "throwaway")
|
||||
delete(raw, "Size")
|
||||
// Add the history and rootfs information.
|
||||
rootfs, err := json.Marshal(rootFS)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error encoding rootfs information %#v: %v", rootFS, err)
|
||||
}
|
||||
rawRootfs := json.RawMessage(rootfs)
|
||||
raw["rootfs"] = &rawRootfs
|
||||
history, err := json.Marshal(convertedHistory)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error encoding history information %#v: %v", convertedHistory, err)
|
||||
}
|
||||
rawHistory := json.RawMessage(history)
|
||||
raw["history"] = &rawHistory
|
||||
// Encode the result.
|
||||
config, err = json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("error re-encoding compat image config %#v: %v", s1, err)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ImageID computes an ID which can uniquely identify this image by its contents.
|
||||
func (m *Schema1) ImageID(diffIDs []digest.Digest) (string, error) {
|
||||
image, err := m.ToSchema2Config(diffIDs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return digest.FromBytes(image).Hex(), nil
|
||||
}
|
||||
254
vendor/github.com/containers/image/manifest/docker_schema2.go
generated
vendored
Normal file
254
vendor/github.com/containers/image/manifest/docker_schema2.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/pkg/strslice"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Schema2Descriptor is a “descriptor” in docker/distribution schema 2.
|
||||
type Schema2Descriptor struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Size int64 `json:"size"`
|
||||
Digest digest.Digest `json:"digest"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
|
||||
// BlobInfoFromSchema2Descriptor returns a types.BlobInfo based on the input schema 2 descriptor.
|
||||
func BlobInfoFromSchema2Descriptor(desc Schema2Descriptor) types.BlobInfo {
|
||||
return types.BlobInfo{
|
||||
Digest: desc.Digest,
|
||||
Size: desc.Size,
|
||||
URLs: desc.URLs,
|
||||
MediaType: desc.MediaType,
|
||||
}
|
||||
}
|
||||
|
||||
// Schema2 is a manifest in docker/distribution schema 2.
|
||||
type Schema2 struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType"`
|
||||
ConfigDescriptor Schema2Descriptor `json:"config"`
|
||||
LayersDescriptors []Schema2Descriptor `json:"layers"`
|
||||
}
|
||||
|
||||
// Schema2Port is a Port, a string containing port number and protocol in the
|
||||
// format "80/tcp", from docker/go-connections/nat.
|
||||
type Schema2Port string
|
||||
|
||||
// Schema2PortSet is a PortSet, a collection of structs indexed by Port, from
|
||||
// docker/go-connections/nat.
|
||||
type Schema2PortSet map[Schema2Port]struct{}
|
||||
|
||||
// Schema2HealthConfig is a HealthConfig, which holds configuration settings
|
||||
// for the HEALTHCHECK feature, from docker/docker/api/types/container.
|
||||
type Schema2HealthConfig struct {
|
||||
// Test is the test to perform to check that the container is healthy.
|
||||
// An empty slice means to inherit the default.
|
||||
// The options are:
|
||||
// {} : inherit healthcheck
|
||||
// {"NONE"} : disable healthcheck
|
||||
// {"CMD", args...} : exec arguments directly
|
||||
// {"CMD-SHELL", command} : run command with system's default shell
|
||||
Test []string `json:",omitempty"`
|
||||
|
||||
// Zero means to inherit. Durations are expressed as integer nanoseconds.
|
||||
StartPeriod time.Duration `json:",omitempty"` // StartPeriod is the time to wait after starting before running the first check.
|
||||
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
|
||||
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
|
||||
|
||||
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
|
||||
// Zero means inherit.
|
||||
Retries int `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Schema2Config is a Config in docker/docker/api/types/container.
|
||||
type Schema2Config struct {
|
||||
Hostname string // Hostname
|
||||
Domainname string // Domainname
|
||||
User string // User that will run the command(s) inside the container, also support user:group
|
||||
AttachStdin bool // Attach the standard input, makes possible user interaction
|
||||
AttachStdout bool // Attach the standard output
|
||||
AttachStderr bool // Attach the standard error
|
||||
ExposedPorts Schema2PortSet `json:",omitempty"` // List of exposed ports
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string // List of environment variable to set in the container
|
||||
Cmd strslice.StrSlice // Command to run when starting the container
|
||||
Healthcheck *Schema2HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy
|
||||
ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific)
|
||||
Image string // Name of the image as it was passed by the operator (e.g. could be symbolic)
|
||||
Volumes map[string]struct{} // List of volumes (mounts) used for the container
|
||||
WorkingDir string // Current directory (PWD) in the command will be launched
|
||||
Entrypoint strslice.StrSlice // Entrypoint to run when starting the container
|
||||
NetworkDisabled bool `json:",omitempty"` // Is network disabled
|
||||
MacAddress string `json:",omitempty"` // Mac Address of the container
|
||||
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
|
||||
Labels map[string]string // List of labels set to this container
|
||||
StopSignal string `json:",omitempty"` // Signal to stop a container
|
||||
StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container
|
||||
Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
|
||||
}
|
||||
|
||||
// Schema2V1Image is a V1Image in docker/docker/image.
|
||||
type Schema2V1Image struct {
|
||||
// ID is a unique 64 character identifier of the image
|
||||
ID string `json:"id,omitempty"`
|
||||
// Parent is the ID of the parent image
|
||||
Parent string `json:"parent,omitempty"`
|
||||
// Comment is the commit message that was set when committing the image
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// Created is the timestamp at which the image was created
|
||||
Created time.Time `json:"created"`
|
||||
// Container is the id of the container used to commit
|
||||
Container string `json:"container,omitempty"`
|
||||
// ContainerConfig is the configuration of the container that is committed into the image
|
||||
ContainerConfig Schema2Config `json:"container_config,omitempty"`
|
||||
// DockerVersion specifies the version of Docker that was used to build the image
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
// Author is the name of the author that was specified when committing the image
|
||||
Author string `json:"author,omitempty"`
|
||||
// Config is the configuration of the container received from the client
|
||||
Config *Schema2Config `json:"config,omitempty"`
|
||||
// Architecture is the hardware that the image is build and runs on
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
// OS is the operating system used to build and run the image
|
||||
OS string `json:"os,omitempty"`
|
||||
// Size is the total size of the image including all layers it is composed of
|
||||
Size int64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Schema2RootFS is a description of how to build up an image's root filesystem, from docker/docker/image.
|
||||
type Schema2RootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []digest.Digest `json:"diff_ids,omitempty"`
|
||||
}
|
||||
|
||||
// Schema2History stores build commands that were used to create an image, from docker/docker/image.
|
||||
type Schema2History struct {
|
||||
// Created is the timestamp at which the image was created
|
||||
Created time.Time `json:"created"`
|
||||
// Author is the name of the author that was specified when committing the image
|
||||
Author string `json:"author,omitempty"`
|
||||
// CreatedBy keeps the Dockerfile command used while building the image
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
// Comment is the commit message that was set when committing the image
|
||||
Comment string `json:"comment,omitempty"`
|
||||
// EmptyLayer is set to true if this history item did not generate a
|
||||
// layer. Otherwise, the history item is associated with the next
|
||||
// layer in the RootFS section.
|
||||
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||
}
|
||||
|
||||
// Schema2Image is an Image in docker/docker/image.
|
||||
type Schema2Image struct {
|
||||
Schema2V1Image
|
||||
Parent digest.Digest `json:"parent,omitempty"`
|
||||
RootFS *Schema2RootFS `json:"rootfs,omitempty"`
|
||||
History []Schema2History `json:"history,omitempty"`
|
||||
OSVersion string `json:"os.version,omitempty"`
|
||||
OSFeatures []string `json:"os.features,omitempty"`
|
||||
}
|
||||
|
||||
// Schema2FromManifest creates a Schema2 manifest instance from a manifest blob.
|
||||
func Schema2FromManifest(manifest []byte) (*Schema2, error) {
|
||||
s2 := Schema2{}
|
||||
if err := json.Unmarshal(manifest, &s2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s2, nil
|
||||
}
|
||||
|
||||
// Schema2FromComponents creates an Schema2 manifest instance from the supplied data.
|
||||
func Schema2FromComponents(config Schema2Descriptor, layers []Schema2Descriptor) *Schema2 {
|
||||
return &Schema2{
|
||||
SchemaVersion: 2,
|
||||
MediaType: DockerV2Schema2MediaType,
|
||||
ConfigDescriptor: config,
|
||||
LayersDescriptors: layers,
|
||||
}
|
||||
}
|
||||
|
||||
// Schema2Clone creates a copy of the supplied Schema2 manifest.
|
||||
func Schema2Clone(src *Schema2) *Schema2 {
|
||||
copy := *src
|
||||
return ©
|
||||
}
|
||||
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
func (m *Schema2) ConfigInfo() types.BlobInfo {
|
||||
return BlobInfoFromSchema2Descriptor(m.ConfigDescriptor)
|
||||
}
|
||||
|
||||
// LayerInfos returns a list of LayerInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *Schema2) LayerInfos() []LayerInfo {
|
||||
blobs := []LayerInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, LayerInfo{
|
||||
BlobInfo: BlobInfoFromSchema2Descriptor(layer),
|
||||
EmptyLayer: false,
|
||||
})
|
||||
}
|
||||
return blobs
|
||||
}
|
||||
|
||||
// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
|
||||
func (m *Schema2) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
if len(m.LayersDescriptors) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.LayersDescriptors), len(layerInfos))
|
||||
}
|
||||
original := m.LayersDescriptors
|
||||
m.LayersDescriptors = make([]Schema2Descriptor, len(layerInfos))
|
||||
for i, info := range layerInfos {
|
||||
m.LayersDescriptors[i].MediaType = original[i].MediaType
|
||||
m.LayersDescriptors[i].Digest = info.Digest
|
||||
m.LayersDescriptors[i].Size = info.Size
|
||||
m.LayersDescriptors[i].URLs = info.URLs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize returns the manifest in a blob format.
|
||||
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
||||
func (m *Schema2) Serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
}
|
||||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *Schema2) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
|
||||
config, err := configGetter(m.ConfigInfo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s2 := &Schema2Image{}
|
||||
if err := json.Unmarshal(config, s2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: "",
|
||||
Created: &s2.Created,
|
||||
DockerVersion: s2.DockerVersion,
|
||||
Architecture: s2.Architecture,
|
||||
Os: s2.OS,
|
||||
Layers: layerInfosToStrings(m.LayerInfos()),
|
||||
}
|
||||
if s2.Config != nil {
|
||||
i.Labels = s2.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ImageID computes an ID which can uniquely identify this image by its contents.
|
||||
func (m *Schema2) ImageID([]digest.Digest) (string, error) {
|
||||
if err := m.ConfigDescriptor.Digest.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.ConfigDescriptor.Digest.Hex(), nil
|
||||
}
|
||||
95
vendor/github.com/containers/image/manifest/manifest.go
generated
vendored
95
vendor/github.com/containers/image/manifest/manifest.go
generated
vendored
@@ -2,7 +2,9 @@ package manifest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/libtrust"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -38,6 +40,45 @@ var DefaultRequestedManifestMIMETypes = []string{
|
||||
DockerV2ListMediaType,
|
||||
}
|
||||
|
||||
// Manifest is an interface for parsing, modifying image manifests in isolation.
|
||||
// Callers can either use this abstract interface without understanding the details of the formats,
|
||||
// or instantiate a specific implementation (e.g. manifest.OCI1) and access the public members
|
||||
// directly.
|
||||
//
|
||||
// See types.Image for functionality not limited to manifests, including format conversions and config parsing.
|
||||
// This interface is similar to, but not strictly equivalent to, the equivalent methods in types.Image.
|
||||
type Manifest interface {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
ConfigInfo() types.BlobInfo
|
||||
// LayerInfos returns a list of LayerInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
LayerInfos() []LayerInfo
|
||||
// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
|
||||
UpdateLayerInfos(layerInfos []types.BlobInfo) error
|
||||
|
||||
// ImageID computes an ID which can uniquely identify this image by its contents, irrespective
|
||||
// of which (of possibly more than one simultaneously valid) reference was used to locate the
|
||||
// image, and unchanged by whether or how the layers are compressed. The result takes the form
|
||||
// of the hexadecimal portion of a digest.Digest.
|
||||
ImageID(diffIDs []digest.Digest) (string, error)
|
||||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest,
|
||||
// incorporating information from a configuration blob returned by configGetter, if
|
||||
// the underlying image format is expected to include a configuration blob.
|
||||
Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error)
|
||||
|
||||
// Serialize returns the manifest in a blob format.
|
||||
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
||||
Serialize() ([]byte, error)
|
||||
}
|
||||
|
||||
// LayerInfo is an extended version of types.BlobInfo for low-level users of Manifest.LayerInfos.
|
||||
type LayerInfo struct {
|
||||
types.BlobInfo
|
||||
EmptyLayer bool // The layer is an “empty”/“throwaway” one, and may or may not be physically represented in various transport / storage systems. false if the manifest type does not have the concept.
|
||||
}
|
||||
|
||||
// GuessMIMEType guesses MIME type of a manifest and returns it _if it is recognized_, or "" if unknown or unrecognized.
|
||||
// FIXME? We should, in general, prefer out-of-band MIME type instead of blindly parsing the manifest,
|
||||
// but we may not have such metadata available (e.g. when the manifest is a local file).
|
||||
@@ -147,3 +188,57 @@ func AddDummyV2S1Signature(manifest []byte) ([]byte, error) {
|
||||
func MIMETypeIsMultiImage(mimeType string) bool {
|
||||
return mimeType == DockerV2ListMediaType
|
||||
}
|
||||
|
||||
// NormalizedMIMEType returns the effective MIME type of a manifest MIME type returned by a server,
|
||||
// centralizing various workarounds.
|
||||
func NormalizedMIMEType(input string) string {
|
||||
switch input {
|
||||
// "application/json" is a valid v2s1 value per https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md .
|
||||
// This works for now, when nothing else seems to return "application/json"; if that were not true, the mapping/detection might
|
||||
// need to happen within the ImageSource.
|
||||
case "application/json":
|
||||
return DockerV2Schema1SignedMediaType
|
||||
case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType,
|
||||
imgspecv1.MediaTypeImageManifest,
|
||||
DockerV2Schema2MediaType,
|
||||
DockerV2ListMediaType:
|
||||
return input
|
||||
default:
|
||||
// If it's not a recognized manifest media type, or we have failed determining the type, we'll try one last time
|
||||
// to deserialize using v2s1 as per https://github.com/docker/distribution/blob/master/manifests.go#L108
|
||||
// and https://github.com/docker/distribution/blob/master/manifest/schema1/manifest.go#L50
|
||||
//
|
||||
// Crane registries can also return "text/plain", or pretty much anything else depending on a file extension “recognized” in the tag.
|
||||
// This makes no real sense, but it happens
|
||||
// because requests for manifests are
|
||||
// redirected to a content distribution
|
||||
// network which is configured that way. See https://bugzilla.redhat.com/show_bug.cgi?id=1389442
|
||||
return DockerV2Schema1SignedMediaType
|
||||
}
|
||||
}
|
||||
|
||||
// FromBlob returns a Manifest instance for the specified manifest blob and the corresponding MIME type
|
||||
func FromBlob(manblob []byte, mt string) (Manifest, error) {
|
||||
switch NormalizedMIMEType(mt) {
|
||||
case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType:
|
||||
return Schema1FromManifest(manblob)
|
||||
case imgspecv1.MediaTypeImageManifest:
|
||||
return OCI1FromManifest(manblob)
|
||||
case DockerV2Schema2MediaType:
|
||||
return Schema2FromManifest(manblob)
|
||||
case DockerV2ListMediaType:
|
||||
return nil, fmt.Errorf("Treating manifest lists as individual manifests is not implemented")
|
||||
default: // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values.
|
||||
return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt)
|
||||
}
|
||||
}
|
||||
|
||||
// layerInfosToStrings converts a list of layer infos, presumably obtained from a Manifest.LayerInfos()
|
||||
// method call, into a format suitable for inclusion in a types.ImageInspectInfo structure.
|
||||
func layerInfosToStrings(infos []LayerInfo) []string {
|
||||
layers := make([]string, len(infos))
|
||||
for i, info := range infos {
|
||||
layers[i] = info.Digest.String()
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
129
vendor/github.com/containers/image/manifest/oci.go
generated
vendored
Normal file
129
vendor/github.com/containers/image/manifest/oci.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BlobInfoFromOCI1Descriptor returns a types.BlobInfo based on the input OCI1 descriptor.
|
||||
func BlobInfoFromOCI1Descriptor(desc imgspecv1.Descriptor) types.BlobInfo {
|
||||
return types.BlobInfo{
|
||||
Digest: desc.Digest,
|
||||
Size: desc.Size,
|
||||
URLs: desc.URLs,
|
||||
Annotations: desc.Annotations,
|
||||
MediaType: desc.MediaType,
|
||||
}
|
||||
}
|
||||
|
||||
// OCI1 is a manifest.Manifest implementation for OCI images.
|
||||
// The underlying data from imgspecv1.Manifest is also available.
|
||||
type OCI1 struct {
|
||||
imgspecv1.Manifest
|
||||
}
|
||||
|
||||
// OCI1FromManifest creates an OCI1 manifest instance from a manifest blob.
|
||||
func OCI1FromManifest(manifest []byte) (*OCI1, error) {
|
||||
oci1 := OCI1{}
|
||||
if err := json.Unmarshal(manifest, &oci1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oci1, nil
|
||||
}
|
||||
|
||||
// OCI1FromComponents creates an OCI1 manifest instance from the supplied data.
|
||||
func OCI1FromComponents(config imgspecv1.Descriptor, layers []imgspecv1.Descriptor) *OCI1 {
|
||||
return &OCI1{
|
||||
imgspecv1.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
Config: config,
|
||||
Layers: layers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OCI1Clone creates a copy of the supplied OCI1 manifest.
|
||||
func OCI1Clone(src *OCI1) *OCI1 {
|
||||
return &OCI1{
|
||||
Manifest: src.Manifest,
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
func (m *OCI1) ConfigInfo() types.BlobInfo {
|
||||
return BlobInfoFromOCI1Descriptor(m.Config)
|
||||
}
|
||||
|
||||
// LayerInfos returns a list of LayerInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *OCI1) LayerInfos() []LayerInfo {
|
||||
blobs := []LayerInfo{}
|
||||
for _, layer := range m.Layers {
|
||||
blobs = append(blobs, LayerInfo{
|
||||
BlobInfo: BlobInfoFromOCI1Descriptor(layer),
|
||||
EmptyLayer: false,
|
||||
})
|
||||
}
|
||||
return blobs
|
||||
}
|
||||
|
||||
// UpdateLayerInfos replaces the original layers with the specified BlobInfos (size+digest+urls), in order (the root layer first, and then successive layered layers)
|
||||
func (m *OCI1) UpdateLayerInfos(layerInfos []types.BlobInfo) error {
|
||||
if len(m.Layers) != len(layerInfos) {
|
||||
return errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(m.Layers), len(layerInfos))
|
||||
}
|
||||
original := m.Layers
|
||||
m.Layers = make([]imgspecv1.Descriptor, len(layerInfos))
|
||||
for i, info := range layerInfos {
|
||||
m.Layers[i].MediaType = original[i].MediaType
|
||||
m.Layers[i].Digest = info.Digest
|
||||
m.Layers[i].Size = info.Size
|
||||
m.Layers[i].Annotations = info.Annotations
|
||||
m.Layers[i].URLs = info.URLs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize returns the manifest in a blob format.
|
||||
// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made!
|
||||
func (m *OCI1) Serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
}
|
||||
|
||||
// Inspect returns various information for (skopeo inspect) parsed from the manifest and configuration.
|
||||
func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*types.ImageInspectInfo, error) {
|
||||
config, err := configGetter(m.ConfigInfo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v1 := &imgspecv1.Image{}
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d1 := &Schema2V1Image{}
|
||||
json.Unmarshal(config, d1)
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: "",
|
||||
Created: v1.Created,
|
||||
DockerVersion: d1.DockerVersion,
|
||||
Labels: v1.Config.Labels,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
Layers: layerInfosToStrings(m.LayerInfos()),
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ImageID computes an ID which can uniquely identify this image by its contents.
|
||||
func (m *OCI1) ImageID([]digest.Digest) (string, error) {
|
||||
if err := m.Config.Digest.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return m.Config.Digest.Hex(), nil
|
||||
}
|
||||
46
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
46
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@@ -16,12 +17,12 @@ type ociArchiveImageDestination struct {
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ctx *types.SystemContext, ref ociArchiveReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (types.ImageDestination, error) {
|
||||
tempDirRef, err := createOCIRef(ref.image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating oci reference")
|
||||
}
|
||||
unpackedDest, err := tempDirRef.ociRefExtracted.NewImageDestination(ctx)
|
||||
unpackedDest, err := tempDirRef.ociRefExtracted.NewImageDestination(ctx, sys)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error deleting temp directory", tempDirRef.tempDirectory)
|
||||
@@ -50,13 +51,12 @@ func (d *ociArchiveImageDestination) SupportedManifestMIMETypes() []string {
|
||||
}
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures
|
||||
func (d *ociArchiveImageDestination) SupportsSignatures() error {
|
||||
return d.unpackedDest.SupportsSignatures()
|
||||
func (d *ociArchiveImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return d.unpackedDest.SupportsSignatures(ctx)
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination
|
||||
func (d *ociArchiveImageDestination) ShouldCompressLayers() bool {
|
||||
return d.unpackedDest.ShouldCompressLayers()
|
||||
func (d *ociArchiveImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return d.unpackedDest.DesiredLayerCompression()
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -70,35 +70,42 @@ func (d *ociArchiveImageDestination) MustMatchRuntimeOS() bool {
|
||||
return d.unpackedDest.MustMatchRuntimeOS()
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *ociArchiveImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return d.unpackedDest.IgnoresEmbeddedDockerReference()
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
func (d *ociArchiveImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlob(stream, inputInfo)
|
||||
func (d *ociArchiveImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.PutBlob(ctx, stream, inputInfo, isConfig)
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob
|
||||
func (d *ociArchiveImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
return d.unpackedDest.HasBlob(info)
|
||||
func (d *ociArchiveImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
return d.unpackedDest.HasBlob(ctx, info)
|
||||
}
|
||||
|
||||
func (d *ociArchiveImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.ReapplyBlob(info)
|
||||
func (d *ociArchiveImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.unpackedDest.ReapplyBlob(ctx, info)
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination
|
||||
func (d *ociArchiveImageDestination) PutManifest(m []byte) error {
|
||||
return d.unpackedDest.PutManifest(m)
|
||||
func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte) error {
|
||||
return d.unpackedDest.PutManifest(ctx, m)
|
||||
}
|
||||
|
||||
func (d *ociArchiveImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
return d.unpackedDest.PutSignatures(signatures)
|
||||
func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
return d.unpackedDest.PutSignatures(ctx, signatures)
|
||||
}
|
||||
|
||||
// Commit marks the process of storing the image as successful and asks for the image to be persisted
|
||||
// after the directory is made, it is tarred up into a file and the directory is deleted
|
||||
func (d *ociArchiveImageDestination) Commit() error {
|
||||
if err := d.unpackedDest.Commit(); err != nil {
|
||||
func (d *ociArchiveImageDestination) Commit(ctx context.Context) error {
|
||||
if err := d.unpackedDest.Commit(ctx); err != nil {
|
||||
return errors.Wrapf(err, "error storing image %q", d.ref.image)
|
||||
}
|
||||
|
||||
@@ -125,6 +132,7 @@ func tarDirectory(src, dst string) error {
|
||||
defer outFile.Close()
|
||||
|
||||
// copies the contents of the directory to the tar file
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using a context.Context.
|
||||
_, err = io.Copy(outFile, input)
|
||||
|
||||
return err
|
||||
|
||||
17
vendor/github.com/containers/image/oci/archive/oci_src.go
generated
vendored
17
vendor/github.com/containers/image/oci/archive/oci_src.go
generated
vendored
@@ -19,13 +19,13 @@ type ociArchiveImageSource struct {
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
// newImageSource untars the file and saves it in a temp directory
|
||||
func newImageSource(ctx *types.SystemContext, ref ociArchiveReference) (types.ImageSource, error) {
|
||||
func newImageSource(ctx context.Context, sys *types.SystemContext, ref ociArchiveReference) (types.ImageSource, error) {
|
||||
tempDirRef, err := createUntarTempDir(ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating temp directory")
|
||||
}
|
||||
|
||||
unpackedSrc, err := tempDirRef.ociRefExtracted.NewImageSource(ctx)
|
||||
unpackedSrc, err := tempDirRef.ociRefExtracted.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error deleting temp directory", tempDirRef.tempDirectory)
|
||||
@@ -72,13 +72,13 @@ func (s *ociArchiveImageSource) Close() error {
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *ociArchiveImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
return s.unpackedSrc.GetManifest(instanceDigest)
|
||||
func (s *ociArchiveImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
return s.unpackedSrc.GetManifest(ctx, instanceDigest)
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||
func (s *ociArchiveImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
return s.unpackedSrc.GetBlob(info)
|
||||
func (s *ociArchiveImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
return s.unpackedSrc.GetBlob(ctx, info)
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
@@ -88,3 +88,8 @@ func (s *ociArchiveImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int
|
||||
func (s *ociArchiveImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
return s.unpackedSrc.GetSignatures(ctx, instanceDigest)
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *ociArchiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
80
vendor/github.com/containers/image/oci/archive/oci_transport.go
generated
vendored
80
vendor/github.com/containers/image/oci/archive/oci_transport.go
generated
vendored
@@ -1,16 +1,17 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/oci/internal"
|
||||
ocilayout "github.com/containers/image/oci/layout"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
@@ -48,51 +49,12 @@ func (t ociArchiveTransport) ParseReference(reference string) (types.ImageRefere
|
||||
|
||||
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
|
||||
func (t ociArchiveTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
var file string
|
||||
sep := strings.SplitN(scope, ":", 2)
|
||||
file = sep[0]
|
||||
|
||||
if len(sep) == 2 {
|
||||
image := sep[1]
|
||||
if !refRegexp.MatchString(image) {
|
||||
return errors.Errorf("Invalid image %s", image)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(file, "/") {
|
||||
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
}
|
||||
// Refuse also "/", otherwise "/" and "" would have the same semantics,
|
||||
// and "" could be unexpectedly shadowed by the "/" entry.
|
||||
// (Note: we do allow "/:someimage", a bit ridiculous but why refuse it?)
|
||||
if scope == "/" {
|
||||
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
|
||||
}
|
||||
cleaned := filepath.Clean(file)
|
||||
if cleaned != file {
|
||||
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
return nil
|
||||
return internal.ValidateScope(scope)
|
||||
}
|
||||
|
||||
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
|
||||
const (
|
||||
separator = `(?:[-._:@+]|--)`
|
||||
alphanum = `(?:[A-Za-z0-9]+)`
|
||||
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
|
||||
)
|
||||
|
||||
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
|
||||
func ParseReference(reference string) (types.ImageReference, error) {
|
||||
var file, image string
|
||||
sep := strings.SplitN(reference, ":", 2)
|
||||
file = sep[0]
|
||||
|
||||
if len(sep) == 2 {
|
||||
image = sep[1]
|
||||
}
|
||||
file, image := internal.SplitPathAndImage(reference)
|
||||
return NewReference(file, image)
|
||||
}
|
||||
|
||||
@@ -102,14 +64,15 @@ func NewReference(file, image string) (types.ImageReference, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
|
||||
// from being ambiguous with values of PolicyConfigurationIdentity.
|
||||
if strings.Contains(resolved, ":") {
|
||||
return nil, errors.Errorf("Invalid OCI reference %s:%s: path %s contains a colon", file, image, resolved)
|
||||
|
||||
if err := internal.ValidateOCIPath(file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(image) > 0 && !refRegexp.MatchString(image) {
|
||||
return nil, errors.Errorf("Invalid image %s", image)
|
||||
|
||||
if err := internal.ValidateImageName(image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ociArchiveReference{file: file, resolvedFile: resolved, image: image}, nil
|
||||
}
|
||||
|
||||
@@ -159,28 +122,28 @@ func (ref ociArchiveReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref ociArchiveReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
func (ref ociArchiveReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, src)
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ociArchiveReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
func (ref ociArchiveReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ociArchiveReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref ociArchiveReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociArchiveReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref ociArchiveReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
@@ -197,7 +160,7 @@ func (t *tempDirOCIRef) deleteTempDir() error {
|
||||
|
||||
// createOCIRef creates the oci reference of the image
|
||||
func createOCIRef(image string) (tempDirOCIRef, error) {
|
||||
dir, err := ioutil.TempDir("/var/tmp", "oci")
|
||||
dir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(), "oci")
|
||||
if err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "error creating temp directory")
|
||||
}
|
||||
@@ -218,6 +181,7 @@ func createUntarTempDir(ref ociArchiveReference) (tempDirOCIRef, error) {
|
||||
}
|
||||
src := ref.resolvedFile
|
||||
dst := tempDirRef.tempDirectory
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using a context.Context.
|
||||
if err := archive.UntarPath(src, dst); err != nil {
|
||||
if err := tempDirRef.deleteTempDir(); err != nil {
|
||||
return tempDirOCIRef{}, errors.Wrapf(err, "error deleting temp directory %q", tempDirRef.tempDirectory)
|
||||
|
||||
126
vendor/github.com/containers/image/oci/internal/oci_util.go
generated
vendored
Normal file
126
vendor/github.com/containers/image/oci/internal/oci_util.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
|
||||
const (
|
||||
separator = `(?:[-._:@+]|--)`
|
||||
alphanum = `(?:[A-Za-z0-9]+)`
|
||||
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
|
||||
)
|
||||
|
||||
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
|
||||
var windowsRefRegexp = regexp.MustCompile(`^([a-zA-Z]:\\.+?):(.*)$`)
|
||||
|
||||
// ValidateImageName returns nil if the image name is empty or matches the open-containers image name specs.
|
||||
// In any other case an error is returned.
|
||||
func ValidateImageName(image string) error {
|
||||
if len(image) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if !refRegexp.MatchString(image) {
|
||||
err = errors.Errorf("Invalid image %s", image)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SplitPathAndImage tries to split the provided OCI reference into the OCI path and image.
|
||||
// Neither path nor image parts are validated at this stage.
|
||||
func SplitPathAndImage(reference string) (string, string) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return splitPathAndImageWindows(reference)
|
||||
}
|
||||
return splitPathAndImageNonWindows(reference)
|
||||
}
|
||||
|
||||
func splitPathAndImageWindows(reference string) (string, string) {
|
||||
groups := windowsRefRegexp.FindStringSubmatch(reference)
|
||||
// nil group means no match
|
||||
if groups == nil {
|
||||
return reference, ""
|
||||
}
|
||||
|
||||
// we expect three elements. First one full match, second the capture group for the path and
|
||||
// the third the capture group for the image
|
||||
if len(groups) != 3 {
|
||||
return reference, ""
|
||||
}
|
||||
return groups[1], groups[2]
|
||||
}
|
||||
|
||||
func splitPathAndImageNonWindows(reference string) (string, string) {
|
||||
sep := strings.SplitN(reference, ":", 2)
|
||||
path := sep[0]
|
||||
|
||||
var image string
|
||||
if len(sep) == 2 {
|
||||
image = sep[1]
|
||||
}
|
||||
return path, image
|
||||
}
|
||||
|
||||
// ValidateOCIPath takes the OCI path and validates it.
|
||||
func ValidateOCIPath(path string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows we must allow for a ':' as part of the path
|
||||
if strings.Count(path, ":") > 1 {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(path, ":") {
|
||||
return errors.Errorf("Invalid OCI reference: path %s contains a colon", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateScope validates a policy configuration scope for an OCI transport.
|
||||
func ValidateScope(scope string) error {
|
||||
var err error
|
||||
if runtime.GOOS == "windows" {
|
||||
err = validateScopeWindows(scope)
|
||||
} else {
|
||||
err = validateScopeNonWindows(scope)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleaned := filepath.Clean(scope)
|
||||
if cleaned != scope {
|
||||
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateScopeWindows(scope string) error {
|
||||
matched, _ := regexp.Match(`^[a-zA-Z]:\\`, []byte(scope))
|
||||
if !matched {
|
||||
return errors.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateScopeNonWindows(scope string) error {
|
||||
if !strings.HasPrefix(scope, "/") {
|
||||
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
}
|
||||
|
||||
// Refuse also "/", otherwise "/" and "" would have the same semantics,
|
||||
// and "" could be unexpectedly shadowed by the "/" entry.
|
||||
if scope == "/" {
|
||||
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
84
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
84
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -8,27 +9,23 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspec "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ociImageDestination struct {
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
acceptUncompressedLayers bool
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ctx *types.SystemContext, ref ociReference) (types.ImageDestination, error) {
|
||||
if ref.image == "" {
|
||||
return nil, errors.Errorf("cannot save image with empty image.ref.name")
|
||||
}
|
||||
|
||||
func newImageDestination(sys *types.SystemContext, ref ociReference) (types.ImageDestination, error) {
|
||||
var index *imgspecv1.Index
|
||||
if indexExists(ref) {
|
||||
var err error
|
||||
@@ -45,8 +42,9 @@ func newImageDestination(ctx *types.SystemContext, ref ociReference) (types.Imag
|
||||
}
|
||||
|
||||
d := &ociImageDestination{ref: ref, index: *index}
|
||||
if ctx != nil {
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
if sys != nil {
|
||||
d.sharedBlobDir = sys.OCISharedBlobDirPath
|
||||
d.acceptUncompressedLayers = sys.OCIAcceptUncompressedLayers
|
||||
}
|
||||
|
||||
if err := ensureDirectoryExists(d.ref.dir); err != nil {
|
||||
@@ -80,13 +78,15 @@ func (d *ociImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *ociImageDestination) SupportsSignatures() error {
|
||||
func (d *ociImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *ociImageDestination) ShouldCompressLayers() bool {
|
||||
return true
|
||||
func (d *ociImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
if d.acceptUncompressedLayers {
|
||||
return types.PreserveOriginal
|
||||
}
|
||||
return types.Compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -100,20 +100,30 @@ func (d *ociImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *ociImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, DockerReference() returns nil.
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *ociImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
blobFile, err := ioutil.TempFile(d.ref.dir, "oci-put-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
succeeded := false
|
||||
explicitClosed := false
|
||||
defer func() {
|
||||
blobFile.Close()
|
||||
if !explicitClosed {
|
||||
blobFile.Close()
|
||||
}
|
||||
if !succeeded {
|
||||
os.Remove(blobFile.Name())
|
||||
}
|
||||
@@ -122,6 +132,7 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
size, err := io.Copy(blobFile, tee)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -133,8 +144,15 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
if err := blobFile.Sync(); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
if err := blobFile.Chmod(0644); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
|
||||
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
|
||||
// On Windows, the “permissions of newly created files” argument to syscall.Open is
|
||||
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
|
||||
// always fails on Windows.
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := blobFile.Chmod(0644); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
blobPath, err := d.ref.blobPath(computedDigest, d.sharedBlobDir)
|
||||
@@ -144,6 +162,10 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
if err := ensureParentDirectoryExists(blobPath); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
// need to explicitly close the file, since a rename won't otherwise not work on Windows
|
||||
blobFile.Close()
|
||||
explicitClosed = true
|
||||
if err := os.Rename(blobFile.Name(), blobPath); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
@@ -155,7 +177,7 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *ociImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
func (d *ociImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
@@ -173,7 +195,7 @@ func (d *ociImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error)
|
||||
return true, finfo.Size(), nil
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *ociImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -181,7 +203,7 @@ func (d *ociImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo,
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *ociImageDestination) PutManifest(m []byte) error {
|
||||
func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte) error {
|
||||
digest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -203,13 +225,11 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.ref.image == "" {
|
||||
return errors.Errorf("cannot save image with empyt image.ref.name")
|
||||
if d.ref.image != "" {
|
||||
annotations := make(map[string]string)
|
||||
annotations["org.opencontainers.image.ref.name"] = d.ref.image
|
||||
desc.Annotations = annotations
|
||||
}
|
||||
|
||||
annotations := make(map[string]string)
|
||||
annotations["org.opencontainers.image.ref.name"] = d.ref.image
|
||||
desc.Annotations = annotations
|
||||
desc.Platform = &imgspecv1.Platform{
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
@@ -230,7 +250,7 @@ func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
|
||||
d.index.Manifests = append(d.index.Manifests, *desc)
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
if len(signatures) != 0 {
|
||||
return errors.Errorf("Pushing signatures for OCI images is not supported")
|
||||
}
|
||||
@@ -241,7 +261,7 @@ func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *ociImageDestination) Commit() error {
|
||||
func (d *ociImageDestination) Commit(ctx context.Context) error {
|
||||
if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
34
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
34
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
@@ -24,15 +24,15 @@ type ociImageSource struct {
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
func newImageSource(ctx *types.SystemContext, ref ociReference) (types.ImageSource, error) {
|
||||
func newImageSource(sys *types.SystemContext, ref ociReference) (types.ImageSource, error) {
|
||||
tr := tlsclientconfig.NewTransport()
|
||||
tr.TLSClientConfig = tlsconfig.ServerDefault()
|
||||
|
||||
if ctx != nil && ctx.OCICertPath != "" {
|
||||
if err := tlsclientconfig.SetupCertificates(ctx.OCICertPath, tr.TLSClientConfig); err != nil {
|
||||
if sys != nil && sys.OCICertPath != "" {
|
||||
if err := tlsclientconfig.SetupCertificates(sys.OCICertPath, tr.TLSClientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.TLSClientConfig.InsecureSkipVerify = ctx.OCIInsecureSkipTLSVerify
|
||||
tr.TLSClientConfig.InsecureSkipVerify = sys.OCIInsecureSkipTLSVerify
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
@@ -42,9 +42,9 @@ func newImageSource(ctx *types.SystemContext, ref ociReference) (types.ImageSour
|
||||
return nil, err
|
||||
}
|
||||
d := &ociImageSource{ref: ref, descriptor: descriptor, client: client}
|
||||
if ctx != nil {
|
||||
if sys != nil {
|
||||
// TODO(jonboulle): check dir existence?
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
d.sharedBlobDir = sys.OCISharedBlobDirPath
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (s *ociImageSource) Close() error {
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *ociImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
func (s *ociImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
var dig digest.Digest
|
||||
var mimeType string
|
||||
if instanceDigest == nil {
|
||||
@@ -93,9 +93,9 @@ func (s *ociImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, str
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||
func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
func (s *ociImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if len(info.URLs) != 0 {
|
||||
return s.getExternalBlob(info.URLs)
|
||||
return s.getExternalBlob(ctx, info.URLs)
|
||||
}
|
||||
|
||||
path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir)
|
||||
@@ -122,10 +122,17 @@ func (s *ociImageSource) GetSignatures(ctx context.Context, instanceDigest *dige
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
|
||||
func (s *ociImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, error) {
|
||||
func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (io.ReadCloser, int64, error) {
|
||||
errWrap := errors.New("failed fetching external blob from all urls")
|
||||
for _, url := range urls {
|
||||
resp, err := s.client.Get(url)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := s.client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
errWrap = errors.Wrapf(errWrap, "fetching %s failed %s", url, err.Error())
|
||||
continue
|
||||
@@ -143,6 +150,11 @@ func (s *ociImageSource) getExternalBlob(urls []string) (io.ReadCloser, int64, e
|
||||
return nil, 0, errWrap
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *ociImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func getBlobSize(resp *http.Response) int64 {
|
||||
size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
|
||||
91
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
91
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
@@ -1,16 +1,17 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/oci/internal"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -22,8 +23,14 @@ func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
|
||||
// Transport is an ImageTransport for OCI directories.
|
||||
var Transport = ociTransport{}
|
||||
var (
|
||||
// Transport is an ImageTransport for OCI directories.
|
||||
Transport = ociTransport{}
|
||||
|
||||
// ErrMoreThanOneImage is an error returned when the manifest includes
|
||||
// more than one image and the user should choose which one to use.
|
||||
ErrMoreThanOneImage = errors.New("more than one image in oci, choose an image")
|
||||
)
|
||||
|
||||
type ociTransport struct{}
|
||||
|
||||
@@ -36,45 +43,12 @@ func (t ociTransport) ParseReference(reference string) (types.ImageReference, er
|
||||
return ParseReference(reference)
|
||||
}
|
||||
|
||||
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
|
||||
const (
|
||||
separator = `(?:[-._:@+]|--)`
|
||||
alphanum = `(?:[A-Za-z0-9]+)`
|
||||
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
|
||||
)
|
||||
|
||||
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
|
||||
|
||||
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
|
||||
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
|
||||
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
|
||||
// scope passed to this function will not be "", that value is always allowed.
|
||||
func (t ociTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
var dir string
|
||||
sep := strings.SplitN(scope, ":", 2)
|
||||
dir = sep[0]
|
||||
|
||||
if len(sep) == 2 {
|
||||
image := sep[1]
|
||||
if !refRegexp.MatchString(image) {
|
||||
return errors.Errorf("Invalid image %s", image)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(dir, "/") {
|
||||
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
|
||||
}
|
||||
// Refuse also "/", otherwise "/" and "" would have the same semantics,
|
||||
// and "" could be unexpectedly shadowed by the "/" entry.
|
||||
// (Note: we do allow "/:someimage", a bit ridiculous but why refuse it?)
|
||||
if scope == "/" {
|
||||
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
|
||||
}
|
||||
cleaned := filepath.Clean(dir)
|
||||
if cleaned != dir {
|
||||
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
|
||||
}
|
||||
return nil
|
||||
return internal.ValidateScope(scope)
|
||||
}
|
||||
|
||||
// ociReference is an ImageReference for OCI directory paths.
|
||||
@@ -87,18 +61,14 @@ type ociReference struct {
|
||||
// (But in general, we make no attempt to be completely safe against concurrent hostile filesystem modifications.)
|
||||
dir string // As specified by the user. May be relative, contain symlinks, etc.
|
||||
resolvedDir string // Absolute path with no symlinks, at least at the time of its creation. Primarily used for policy namespaces.
|
||||
image string // If image=="", it means the only image in the index.json is used
|
||||
// If image=="", it means the "only image" in the index.json is used in the case it is a source
|
||||
// for destinations, the image name annotation "image.ref.name" is not added to the index.json
|
||||
image string
|
||||
}
|
||||
|
||||
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
|
||||
func ParseReference(reference string) (types.ImageReference, error) {
|
||||
var dir, image string
|
||||
sep := strings.SplitN(reference, ":", 2)
|
||||
dir = sep[0]
|
||||
|
||||
if len(sep) == 2 {
|
||||
image = sep[1]
|
||||
}
|
||||
dir, image := internal.SplitPathAndImage(reference)
|
||||
return NewReference(dir, image)
|
||||
}
|
||||
|
||||
@@ -111,14 +81,15 @@ func NewReference(dir, image string) (types.ImageReference, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
|
||||
// from being ambiguous with values of PolicyConfigurationIdentity.
|
||||
if strings.Contains(resolved, ":") {
|
||||
return nil, errors.Errorf("Invalid OCI reference %s:%s: path %s contains a colon", dir, image, resolved)
|
||||
|
||||
if err := internal.ValidateOCIPath(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(image) > 0 && !refRegexp.MatchString(image) {
|
||||
return nil, errors.Errorf("Invalid image %s", image)
|
||||
|
||||
if err = internal.ValidateImageName(image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ociReference{dir: dir, resolvedDir: resolved, image: image}, nil
|
||||
}
|
||||
|
||||
@@ -182,12 +153,12 @@ func (ref ociReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref ociReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
func (ref ociReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, src)
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// getIndex returns a pointer to the index references by this ociReference. If an error occurs opening an index nil is returned together
|
||||
@@ -219,7 +190,7 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
|
||||
d = &index.Manifests[0]
|
||||
} else {
|
||||
// ask user to choose image when more than one image in the oci directory
|
||||
return imgspecv1.Descriptor{}, errors.Wrapf(err, "more than one image in oci, choose an image")
|
||||
return imgspecv1.Descriptor{}, ErrMoreThanOneImage
|
||||
}
|
||||
} else {
|
||||
// if image specified, look through all manifests for a match
|
||||
@@ -255,18 +226,18 @@ func LoadManifestDescriptor(imgRef types.ImageReference) (imgspecv1.Descriptor,
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ociReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
func (ref ociReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(sys, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ociReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref ociReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ociReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref ociReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for oci: images")
|
||||
}
|
||||
|
||||
|
||||
75
vendor/github.com/containers/image/openshift/openshift.go
generated
vendored
75
vendor/github.com/containers/image/openshift/openshift.go
generated
vendored
@@ -7,13 +7,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/iolimits"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/image/version"
|
||||
@@ -102,7 +102,7 @@ func (c *openshiftClient) doRequest(ctx context.Context, method, path string, re
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
body, err := iolimits.ReadAtMost(res.Body, iolimits.MaxOpenShiftStatusBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func (c *openshiftClient) doRequest(ctx context.Context, method, path string, re
|
||||
if statusValid {
|
||||
return nil, errors.New(status.Message)
|
||||
}
|
||||
return nil, errors.Errorf("HTTP error: status code: %d, body: %s", res.StatusCode, string(body))
|
||||
return nil, errors.Errorf("HTTP error: status code: %d (%s), body: %s", res.StatusCode, http.StatusText(res.StatusCode), string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
@@ -162,7 +162,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error
|
||||
type openshiftImageSource struct {
|
||||
client *openshiftClient
|
||||
// Values specific to this image
|
||||
ctx *types.SystemContext
|
||||
sys *types.SystemContext
|
||||
// State
|
||||
docker types.ImageSource // The Docker Registry endpoint, or nil if not resolved yet
|
||||
imageStreamImageName string // Resolved image identifier, or "" if not known yet
|
||||
@@ -170,7 +170,7 @@ type openshiftImageSource struct {
|
||||
|
||||
// newImageSource creates a new ImageSource for the specified reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx *types.SystemContext, ref openshiftReference) (types.ImageSource, error) {
|
||||
func newImageSource(sys *types.SystemContext, ref openshiftReference) (types.ImageSource, error) {
|
||||
client, err := newOpenshiftClient(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -178,7 +178,7 @@ func newImageSource(ctx *types.SystemContext, ref openshiftReference) (types.Ima
|
||||
|
||||
return &openshiftImageSource{
|
||||
client: client,
|
||||
ctx: ctx,
|
||||
sys: sys,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -204,19 +204,19 @@ func (s *openshiftImageSource) Close() error {
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *openshiftImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if err := s.ensureImageIsResolved(context.TODO()); err != nil {
|
||||
func (s *openshiftImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if err := s.ensureImageIsResolved(ctx); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return s.docker.GetManifest(instanceDigest)
|
||||
return s.docker.GetManifest(ctx, instanceDigest)
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *openshiftImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if err := s.ensureImageIsResolved(context.TODO()); err != nil {
|
||||
func (s *openshiftImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
if err := s.ensureImageIsResolved(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return s.docker.GetBlob(info)
|
||||
return s.docker.GetBlob(ctx, info)
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
@@ -246,6 +246,11 @@ func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *openshiftImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ensureImageIsResolved sets up s.docker and s.imageStreamImageName
|
||||
func (s *openshiftImageSource) ensureImageIsResolved(ctx context.Context) error {
|
||||
if s.docker != nil {
|
||||
@@ -286,7 +291,7 @@ func (s *openshiftImageSource) ensureImageIsResolved(ctx context.Context) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d, err := dockerRef.NewImageSource(s.ctx)
|
||||
d, err := dockerRef.NewImageSource(ctx, s.sys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -303,7 +308,7 @@ type openshiftImageDestination struct {
|
||||
}
|
||||
|
||||
// newImageDestination creates a new ImageDestination for the specified reference.
|
||||
func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx context.Context, sys *types.SystemContext, ref openshiftReference) (types.ImageDestination, error) {
|
||||
client, err := newOpenshiftClient(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -317,7 +322,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docker, err := dockerRef.NewImageDestination(ctx)
|
||||
docker, err := dockerRef.NewImageDestination(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -345,13 +350,12 @@ func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *openshiftImageDestination) SupportsSignatures() error {
|
||||
func (d *openshiftImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *openshiftImageDestination) ShouldCompressLayers() bool {
|
||||
return true
|
||||
func (d *openshiftImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.Compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -365,43 +369,50 @@ func (d *openshiftImageDestination) MustMatchRuntimeOS() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *openshiftImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return d.docker.IgnoresEmbeddedDockerReference()
|
||||
}
|
||||
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *openshiftImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.docker.PutBlob(stream, inputInfo)
|
||||
func (d *openshiftImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
return d.docker.PutBlob(ctx, stream, inputInfo, isConfig)
|
||||
}
|
||||
|
||||
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be reapplied using ReapplyBlob.
|
||||
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
|
||||
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
|
||||
// it returns a non-nil error only on an unexpected failure.
|
||||
func (d *openshiftImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
return d.docker.HasBlob(info)
|
||||
func (d *openshiftImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
return d.docker.HasBlob(ctx, info)
|
||||
}
|
||||
|
||||
func (d *openshiftImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.docker.ReapplyBlob(info)
|
||||
func (d *openshiftImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return d.docker.ReapplyBlob(ctx, info)
|
||||
}
|
||||
|
||||
// PutManifest writes manifest to the destination.
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *openshiftImageDestination) PutManifest(m []byte) error {
|
||||
func (d *openshiftImageDestination) PutManifest(ctx context.Context, m []byte) error {
|
||||
manifestDigest, err := manifest.Digest(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.imageStreamImageName = manifestDigest.String()
|
||||
|
||||
return d.docker.PutManifest(m)
|
||||
return d.docker.PutManifest(ctx, m)
|
||||
}
|
||||
|
||||
func (d *openshiftImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
if d.imageStreamImageName == "" {
|
||||
return errors.Errorf("Internal error: Unknown manifest digest, can't add signatures")
|
||||
}
|
||||
@@ -412,7 +423,7 @@ func (d *openshiftImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
return nil // No need to even read the old state.
|
||||
}
|
||||
|
||||
image, err := d.client.getImage(context.TODO(), d.imageStreamImageName)
|
||||
image, err := d.client.getImage(ctx, d.imageStreamImageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -453,7 +464,7 @@ sigExists:
|
||||
Content: newSig,
|
||||
}
|
||||
body, err := json.Marshal(sig)
|
||||
_, err = d.client.doRequest(context.TODO(), "POST", "/oapi/v1/imagesignatures", body)
|
||||
_, err = d.client.doRequest(ctx, "POST", "/oapi/v1/imagesignatures", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -466,8 +477,8 @@ sigExists:
|
||||
// WARNING: This does not have any transactional semantics:
|
||||
// - Uploaded data MAY be visible to others before Commit() is called
|
||||
// - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed)
|
||||
func (d *openshiftImageDestination) Commit() error {
|
||||
return d.docker.Commit()
|
||||
func (d *openshiftImageDestination) Commit(ctx context.Context) error {
|
||||
return d.docker.Commit(ctx)
|
||||
}
|
||||
|
||||
// These structs are subsets of github.com/openshift/origin/pkg/image/api/v1 and its dependencies.
|
||||
|
||||
17
vendor/github.com/containers/image/openshift/openshift_transport.go
generated
vendored
17
vendor/github.com/containers/image/openshift/openshift_transport.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package openshift
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -130,27 +131,27 @@ func (ref openshiftReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref openshiftReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(ctx, ref)
|
||||
func (ref openshiftReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := newImageSource(sys, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return genericImage.FromSource(ctx, src)
|
||||
return genericImage.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref openshiftReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(ctx, ref)
|
||||
func (ref openshiftReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(sys, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref openshiftReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, ref)
|
||||
func (ref openshiftReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ctx, sys, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref openshiftReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref openshiftReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for atomic: images")
|
||||
}
|
||||
|
||||
257
vendor/github.com/containers/image/ostree/ostree_dest.go
generated
vendored
257
vendor/github.com/containers/image/ostree/ostree_dest.go
generated
vendored
@@ -4,6 +4,9 @@ package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -11,19 +14,35 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
selinux "github.com/opencontainers/selinux/go-selinux"
|
||||
"github.com/ostreedev/ostree-go/pkg/otbuiltin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1 libselinux
|
||||
// #include <glib.h>
|
||||
// #include <glib-object.h>
|
||||
// #include <gio/gio.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <ostree.h>
|
||||
// #include <gio/ginputstream.h>
|
||||
// #include <selinux/selinux.h>
|
||||
// #include <selinux/label.h>
|
||||
import "C"
|
||||
|
||||
type blobToImport struct {
|
||||
Size int64
|
||||
Digest digest.Digest
|
||||
@@ -35,18 +54,24 @@ type descriptor struct {
|
||||
Digest digest.Digest `json:"digest"`
|
||||
}
|
||||
|
||||
type fsLayersSchema1 struct {
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
type manifestSchema struct {
|
||||
ConfigDescriptor descriptor `json:"config"`
|
||||
LayersDescriptors []descriptor `json:"layers"`
|
||||
LayersDescriptors []descriptor `json:"layers"`
|
||||
FSLayers []fsLayersSchema1 `json:"fsLayers"`
|
||||
}
|
||||
|
||||
type ostreeImageDestination struct {
|
||||
ref ostreeReference
|
||||
manifest string
|
||||
schema manifestSchema
|
||||
tmpDirPath string
|
||||
blobs map[string]*blobToImport
|
||||
digest digest.Digest
|
||||
ref ostreeReference
|
||||
manifest string
|
||||
schema manifestSchema
|
||||
tmpDirPath string
|
||||
blobs map[string]*blobToImport
|
||||
digest digest.Digest
|
||||
signaturesLen int
|
||||
repo *C.struct_OstreeRepo
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing ostree.
|
||||
@@ -55,7 +80,7 @@ func newImageDestination(ref ostreeReference, tmpDirPath string) (types.ImageDes
|
||||
if err := ensureDirectoryExists(tmpDirPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ostreeImageDestination{ref, "", manifestSchema{}, tmpDirPath, map[string]*blobToImport{}, ""}, nil
|
||||
return &ostreeImageDestination{ref, "", manifestSchema{}, tmpDirPath, map[string]*blobToImport{}, "", 0, nil}, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
@@ -66,6 +91,9 @@ func (d *ostreeImageDestination) Reference() types.ImageReference {
|
||||
|
||||
// Close removes resources associated with an initialized ImageDestination, if any.
|
||||
func (d *ostreeImageDestination) Close() error {
|
||||
if d.repo != nil {
|
||||
C.g_object_unref(C.gpointer(d.repo))
|
||||
}
|
||||
return os.RemoveAll(d.tmpDirPath)
|
||||
}
|
||||
|
||||
@@ -77,13 +105,13 @@ func (d *ostreeImageDestination) SupportedManifestMIMETypes() []string {
|
||||
|
||||
// SupportsSignatures returns an error (to be displayed to the user) if the destination certainly can't store signatures.
|
||||
// Note: It is still possible for PutSignatures to fail if SupportsSignatures returns nil.
|
||||
func (d *ostreeImageDestination) SupportsSignatures() error {
|
||||
func (d *ostreeImageDestination) SupportsSignatures(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *ostreeImageDestination) ShouldCompressLayers() bool {
|
||||
return false
|
||||
func (d *ostreeImageDestination) DesiredLayerCompression() types.LayerCompression {
|
||||
return types.PreserveOriginal
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -97,7 +125,14 @@ func (d *ostreeImageDestination) MustMatchRuntimeOS() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
// IgnoresEmbeddedDockerReference returns true iff the destination does not care about Image.EmbeddedDockerReferenceConflicts(),
|
||||
// and would prefer to receive an unmodified manifest instead of one modified for the destination.
|
||||
// Does not make a difference if Reference().DockerReference() is nil.
|
||||
func (d *ostreeImageDestination) IgnoresEmbeddedDockerReference() bool {
|
||||
return false // N/A, DockerReference() returns nil.
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, isConfig bool) (types.BlobInfo, error) {
|
||||
tmpDir, err := ioutil.TempDir(d.tmpDirPath, "blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -113,6 +148,7 @@ func (d *ostreeImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
digester := digest.Canonical.Digester()
|
||||
tee := io.TeeReader(stream, digester.Hash())
|
||||
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
size, err := io.Copy(blobFile, tee)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -130,7 +166,7 @@ func (d *ostreeImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobI
|
||||
return types.BlobInfo{Digest: computedDigest, Size: size}, nil
|
||||
}
|
||||
|
||||
func fixFiles(dir string, usermode bool) error {
|
||||
func fixFiles(selinuxHnd *C.struct_selabel_handle, root string, dir string, usermode bool) error {
|
||||
entries, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -144,13 +180,43 @@ func fixFiles(dir string, usermode bool) error {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if selinuxHnd != nil {
|
||||
relPath, err := filepath.Rel(root, fullpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Handle /exports/hostfs as a special case. Files under this directory are copied to the host,
|
||||
// thus we benefit from maintaining the same SELinux label they would have on the host as we could
|
||||
// use hard links instead of copying the files.
|
||||
relPath = fmt.Sprintf("/%s", strings.TrimPrefix(relPath, "exports/hostfs/"))
|
||||
|
||||
relPathC := C.CString(relPath)
|
||||
defer C.free(unsafe.Pointer(relPathC))
|
||||
var context *C.char
|
||||
|
||||
res, err := C.selabel_lookup_raw(selinuxHnd, &context, relPathC, C.int(info.Mode()&os.ModePerm))
|
||||
if int(res) < 0 && err != syscall.ENOENT {
|
||||
return errors.Wrapf(err, "cannot selabel_lookup_raw %s", relPath)
|
||||
}
|
||||
if int(res) == 0 {
|
||||
defer C.freecon(context)
|
||||
fullpathC := C.CString(fullpath)
|
||||
defer C.free(unsafe.Pointer(fullpathC))
|
||||
res, err = C.lsetfilecon_raw(fullpathC, context)
|
||||
if int(res) < 0 {
|
||||
return errors.Wrapf(err, "cannot setfilecon_raw %s", fullpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
if usermode {
|
||||
if err := os.Chmod(fullpath, info.Mode()|0700); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = fixFiles(fullpath, usermode)
|
||||
err = fixFiles(selinuxHnd, root, fullpath, usermode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -174,7 +240,41 @@ func (d *ostreeImageDestination) ostreeCommit(repo *otbuiltin.Repo, branch strin
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importBlob(repo *otbuiltin.Repo, blob *blobToImport) error {
|
||||
func generateTarSplitMetadata(output *bytes.Buffer, file string) (digest.Digest, int64, error) {
|
||||
mfz := gzip.NewWriter(output)
|
||||
defer mfz.Close()
|
||||
metaPacker := storage.NewJSONPacker(mfz)
|
||||
|
||||
stream, err := os.OpenFile(file, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
gzReader, err := archive.DecompressStream(stream)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
its, err := asm.NewInputTarStream(gzReader, metaPacker, nil)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
|
||||
written, err := io.Copy(digester.Hash(), its)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
|
||||
return digester.Digest(), written, nil
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importBlob(selinuxHnd *C.struct_selabel_handle, repo *otbuiltin.Repo, blob *blobToImport) error {
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using a context.Context.
|
||||
|
||||
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Hex())
|
||||
destinationPath := filepath.Join(d.tmpDirPath, blob.Digest.Hex(), "root")
|
||||
if err := ensureDirectoryExists(destinationPath); err != nil {
|
||||
@@ -185,11 +285,17 @@ func (d *ostreeImageDestination) importBlob(repo *otbuiltin.Repo, blob *blobToIm
|
||||
os.RemoveAll(destinationPath)
|
||||
}()
|
||||
|
||||
var tarSplitOutput bytes.Buffer
|
||||
uncompressedDigest, uncompressedSize, err := generateTarSplitMetadata(&tarSplitOutput, blob.BlobPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if os.Getuid() == 0 {
|
||||
if err := archive.UntarPath(blob.BlobPath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fixFiles(destinationPath, false); err != nil {
|
||||
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -198,32 +304,51 @@ func (d *ostreeImageDestination) importBlob(repo *otbuiltin.Repo, blob *blobToIm
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fixFiles(destinationPath, true); err != nil {
|
||||
if err := fixFiles(selinuxHnd, destinationPath, destinationPath, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size),
|
||||
fmt.Sprintf("docker.uncompressed_size=%d", uncompressedSize),
|
||||
fmt.Sprintf("docker.uncompressed_digest=%s", uncompressedDigest.String()),
|
||||
fmt.Sprintf("tarsplit.output=%s", base64.StdEncoding.EncodeToString(tarSplitOutput.Bytes()))})
|
||||
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importConfig(repo *otbuiltin.Repo, blob *blobToImport) error {
|
||||
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Hex())
|
||||
destinationPath := filepath.Dir(blob.BlobPath)
|
||||
|
||||
return d.ostreeCommit(repo, ostreeBranch, destinationPath, []string{fmt.Sprintf("docker.size=%d", blob.Size)})
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) importConfig(blob *blobToImport) error {
|
||||
ostreeBranch := fmt.Sprintf("ociimage/%s", blob.Digest.Hex())
|
||||
func (d *ostreeImageDestination) HasBlob(ctx context.Context, info types.BlobInfo) (bool, int64, error) {
|
||||
|
||||
return exec.Command("ostree", "commit",
|
||||
"--repo", d.ref.repo,
|
||||
fmt.Sprintf("--add-metadata-string=docker.size=%d", blob.Size),
|
||||
"--branch", ostreeBranch, filepath.Dir(blob.BlobPath)).Run()
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error) {
|
||||
branch := fmt.Sprintf("ociimage/%s", info.Digest.Hex())
|
||||
output, err := exec.Command("ostree", "show", "--repo", d.ref.repo, "--print-metadata-key=docker.size", branch).CombinedOutput()
|
||||
if err != nil {
|
||||
if bytes.Index(output, []byte("not found")) >= 0 || bytes.Index(output, []byte("No such")) >= 0 {
|
||||
return false, -1, nil
|
||||
if d.repo == nil {
|
||||
repo, err := openRepo(d.ref.repo)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
return false, -1, err
|
||||
d.repo = repo
|
||||
}
|
||||
size, err := strconv.ParseInt(strings.Trim(string(output), "'\n"), 10, 64)
|
||||
branch := fmt.Sprintf("ociimage/%s", info.Digest.Hex())
|
||||
|
||||
found, data, err := readMetadata(d.repo, branch, "docker.uncompressed_digest")
|
||||
if err != nil || !found {
|
||||
return found, -1, err
|
||||
}
|
||||
|
||||
found, data, err = readMetadata(d.repo, branch, "docker.uncompressed_size")
|
||||
if err != nil || !found {
|
||||
return found, -1, err
|
||||
}
|
||||
|
||||
found, data, err = readMetadata(d.repo, branch, "docker.size")
|
||||
if err != nil || !found {
|
||||
return found, -1, err
|
||||
}
|
||||
|
||||
size, err := strconv.ParseInt(data, 10, 64)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
@@ -231,7 +356,7 @@ func (d *ostreeImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro
|
||||
return true, size, nil
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *ostreeImageDestination) ReapplyBlob(ctx context.Context, info types.BlobInfo) (types.BlobInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -239,7 +364,7 @@ func (d *ostreeImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInf
|
||||
// FIXME? This should also receive a MIME type if known, to differentiate between schema versions.
|
||||
// If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema),
|
||||
// but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError.
|
||||
func (d *ostreeImageDestination) PutManifest(manifestBlob []byte) error {
|
||||
func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error {
|
||||
d.manifest = string(manifestBlob)
|
||||
|
||||
if err := json.Unmarshal(manifestBlob, &d.schema); err != nil {
|
||||
@@ -260,7 +385,7 @@ func (d *ostreeImageDestination) PutManifest(manifestBlob []byte) error {
|
||||
return ioutil.WriteFile(manifestPath, manifestBlob, 0644)
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
|
||||
path := filepath.Join(d.tmpDirPath, d.ref.signaturePath(0))
|
||||
if err := ensureParentDirectoryExists(path); err != nil {
|
||||
return err
|
||||
@@ -272,10 +397,14 @@ func (d *ostreeImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
d.signaturesLen = len(signatures)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ostreeImageDestination) Commit() error {
|
||||
func (d *ostreeImageDestination) Commit(ctx context.Context) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
repo, err := otbuiltin.OpenRepo(d.ref.repo)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -286,24 +415,48 @@ func (d *ostreeImageDestination) Commit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, layer := range d.schema.LayersDescriptors {
|
||||
hash := layer.Digest.Hex()
|
||||
var selinuxHnd *C.struct_selabel_handle
|
||||
|
||||
if os.Getuid() == 0 && selinux.GetEnabled() {
|
||||
selinuxHnd, err = C.selabel_open(C.SELABEL_CTX_FILE, nil, 0)
|
||||
if selinuxHnd == nil {
|
||||
return errors.Wrapf(err, "cannot open the SELinux DB")
|
||||
}
|
||||
|
||||
defer C.selabel_close(selinuxHnd)
|
||||
}
|
||||
|
||||
checkLayer := func(hash string) error {
|
||||
blob := d.blobs[hash]
|
||||
// if the blob is not present in d.blobs then it is already stored in OSTree,
|
||||
// and we don't need to import it.
|
||||
if blob == nil {
|
||||
continue
|
||||
return nil
|
||||
}
|
||||
err := d.importBlob(repo, blob)
|
||||
err := d.importBlob(selinuxHnd, repo, blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(d.blobs, hash)
|
||||
return nil
|
||||
}
|
||||
for _, layer := range d.schema.LayersDescriptors {
|
||||
hash := layer.Digest.Hex()
|
||||
if err = checkLayer(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, layer := range d.schema.FSLayers {
|
||||
hash := layer.BlobSum.Hex()
|
||||
if err = checkLayer(hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hash := d.schema.ConfigDescriptor.Digest.Hex()
|
||||
blob := d.blobs[hash]
|
||||
if blob != nil {
|
||||
err := d.importConfig(blob)
|
||||
// Import the other blobs that are not layers
|
||||
for _, blob := range d.blobs {
|
||||
err := d.importConfig(repo, blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -311,8 +464,12 @@ func (d *ostreeImageDestination) Commit() error {
|
||||
|
||||
manifestPath := filepath.Join(d.tmpDirPath, "manifest")
|
||||
|
||||
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)), fmt.Sprintf("docker.digest=%s", string(d.digest))}
|
||||
err = d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata)
|
||||
metadata := []string{fmt.Sprintf("docker.manifest=%s", string(d.manifest)),
|
||||
fmt.Sprintf("signatures=%d", d.signaturesLen),
|
||||
fmt.Sprintf("docker.digest=%s", string(d.digest))}
|
||||
if err := d.ostreeCommit(repo, fmt.Sprintf("ociimage/%s", d.ref.branchName), manifestPath, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = repo.CommitTransaction()
|
||||
return err
|
||||
|
||||
408
vendor/github.com/containers/image/ostree/ostree_src.go
generated
vendored
Normal file
408
vendor/github.com/containers/image/ostree/ostree_src.go
generated
vendored
Normal file
@@ -0,0 +1,408 @@
|
||||
// +build !containers_image_ostree_stub
|
||||
|
||||
package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
"github.com/opencontainers/go-digest"
|
||||
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vbatts/tar-split/tar/asm"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
)
|
||||
|
||||
// #cgo pkg-config: glib-2.0 gobject-2.0 ostree-1
|
||||
// #include <glib.h>
|
||||
// #include <glib-object.h>
|
||||
// #include <gio/gio.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <ostree.h>
|
||||
// #include <gio/ginputstream.h>
|
||||
import "C"
|
||||
|
||||
type ostreeImageSource struct {
|
||||
ref ostreeReference
|
||||
tmpDir string
|
||||
repo *C.struct_OstreeRepo
|
||||
// get the compressed layer by its uncompressed checksum
|
||||
compressed map[digest.Digest]digest.Digest
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
func newImageSource(tmpDir string, ref ostreeReference) (types.ImageSource, error) {
|
||||
return &ostreeImageSource{ref: ref, tmpDir: tmpDir, compressed: nil}, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
func (s *ostreeImageSource) Reference() types.ImageReference {
|
||||
return s.ref
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized ImageSource, if any.
|
||||
func (s *ostreeImageSource) Close() error {
|
||||
if s.repo != nil {
|
||||
C.g_object_unref(C.gpointer(s.repo))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getLayerSize(blob string) (int64, error) {
|
||||
b := fmt.Sprintf("ociimage/%s", blob)
|
||||
found, data, err := readMetadata(s.repo, b, "docker.size")
|
||||
if err != nil || !found {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(data, 10, 64)
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getLenSignatures() (int64, error) {
|
||||
b := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
found, data, err := readMetadata(s.repo, b, "signatures")
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if !found {
|
||||
// if 'signatures' is not present, just return 0 signatures.
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseInt(data, 10, 64)
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) getTarSplitData(blob string) ([]byte, error) {
|
||||
b := fmt.Sprintf("ociimage/%s", blob)
|
||||
found, out, err := readMetadata(s.repo, b, "tarsplit.output")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(out)
|
||||
}
|
||||
|
||||
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
||||
// It may use a remote (= slow) service.
|
||||
func (s *ostreeImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, "", errors.Errorf(`Manifest lists are not supported by "ostree:"`)
|
||||
}
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
b := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
found, out, err := readMetadata(s.repo, b, "docker.manifest")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if !found {
|
||||
return nil, "", errors.New("manifest not found")
|
||||
}
|
||||
m := []byte(out)
|
||||
return m, manifest.GuessMIMEType(m), nil
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
return nil, "", errors.New("manifest lists are not supported by this transport")
|
||||
}
|
||||
|
||||
func openRepo(path string) (*C.struct_OstreeRepo, error) {
|
||||
var cerr *C.GError
|
||||
cpath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cpath))
|
||||
pathc := C.g_file_new_for_path(cpath)
|
||||
defer C.g_object_unref(C.gpointer(pathc))
|
||||
repo := C.ostree_repo_new(pathc)
|
||||
r := glib.GoBool(glib.GBoolean(C.ostree_repo_open(repo, nil, &cerr)))
|
||||
if !r {
|
||||
C.g_object_unref(C.gpointer(repo))
|
||||
return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
type ostreePathFileGetter struct {
|
||||
repo *C.struct_OstreeRepo
|
||||
parentRoot *C.GFile
|
||||
}
|
||||
|
||||
type ostreeReader struct {
|
||||
stream *C.GFileInputStream
|
||||
}
|
||||
|
||||
func (o ostreeReader) Close() error {
|
||||
C.g_object_unref(C.gpointer(o.stream))
|
||||
return nil
|
||||
}
|
||||
func (o ostreeReader) Read(p []byte) (int, error) {
|
||||
var cerr *C.GError
|
||||
instanceCast := C.g_type_check_instance_cast((*C.GTypeInstance)(unsafe.Pointer(o.stream)), C.g_input_stream_get_type())
|
||||
stream := (*C.GInputStream)(unsafe.Pointer(instanceCast))
|
||||
|
||||
b := C.g_input_stream_read_bytes(stream, (C.gsize)(cap(p)), nil, &cerr)
|
||||
if b == nil {
|
||||
return 0, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
defer C.g_bytes_unref(b)
|
||||
|
||||
count := int(C.g_bytes_get_size(b))
|
||||
if count == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
data := (*[1 << 30]byte)(unsafe.Pointer(C.g_bytes_get_data(b, nil)))[:count:count]
|
||||
copy(p, data)
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func readMetadata(repo *C.struct_OstreeRepo, commit, key string) (bool, string, error) {
|
||||
var cerr *C.GError
|
||||
var ref *C.char
|
||||
defer C.free(unsafe.Pointer(ref))
|
||||
|
||||
cCommit := C.CString(commit)
|
||||
defer C.free(unsafe.Pointer(cCommit))
|
||||
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(repo, cCommit, C.gboolean(1), &ref, &cerr))) {
|
||||
return false, "", glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
var variant *C.GVariant
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_load_variant(repo, C.OSTREE_OBJECT_TYPE_COMMIT, ref, &variant, &cerr))) {
|
||||
return false, "", glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
defer C.g_variant_unref(variant)
|
||||
if variant != nil {
|
||||
cKey := C.CString(key)
|
||||
defer C.free(unsafe.Pointer(cKey))
|
||||
|
||||
metadata := C.g_variant_get_child_value(variant, 0)
|
||||
defer C.g_variant_unref(metadata)
|
||||
|
||||
data := C.g_variant_lookup_value(metadata, (*C.gchar)(cKey), nil)
|
||||
if data != nil {
|
||||
defer C.g_variant_unref(data)
|
||||
ptr := (*C.char)(C.g_variant_get_string(data, nil))
|
||||
val := C.GoString(ptr)
|
||||
return true, val, nil
|
||||
}
|
||||
}
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
func newOSTreePathFileGetter(repo *C.struct_OstreeRepo, commit string) (*ostreePathFileGetter, error) {
|
||||
var cerr *C.GError
|
||||
var parentRoot *C.GFile
|
||||
cCommit := C.CString(commit)
|
||||
defer C.free(unsafe.Pointer(cCommit))
|
||||
if !glib.GoBool(glib.GBoolean(C.ostree_repo_read_commit(repo, cCommit, &parentRoot, nil, nil, &cerr))) {
|
||||
return &ostreePathFileGetter{}, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
C.g_object_ref(C.gpointer(repo))
|
||||
|
||||
return &ostreePathFileGetter{repo: repo, parentRoot: parentRoot}, nil
|
||||
}
|
||||
|
||||
func (o ostreePathFileGetter) Get(filename string) (io.ReadCloser, error) {
|
||||
var file *C.GFile
|
||||
if strings.HasPrefix(filename, "./") {
|
||||
filename = filename[2:]
|
||||
}
|
||||
cfilename := C.CString(filename)
|
||||
defer C.free(unsafe.Pointer(cfilename))
|
||||
|
||||
file = (*C.GFile)(C.g_file_resolve_relative_path(o.parentRoot, cfilename))
|
||||
|
||||
var cerr *C.GError
|
||||
stream := C.g_file_read(file, nil, &cerr)
|
||||
if stream == nil {
|
||||
return nil, glib.ConvertGError(glib.ToGError(unsafe.Pointer(cerr)))
|
||||
}
|
||||
|
||||
return &ostreeReader{stream: stream}, nil
|
||||
}
|
||||
|
||||
func (o ostreePathFileGetter) Close() {
|
||||
C.g_object_unref(C.gpointer(o.repo))
|
||||
C.g_object_unref(C.gpointer(o.parentRoot))
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) readSingleFile(commit, path string) (io.ReadCloser, error) {
|
||||
getter, err := newOSTreePathFileGetter(s.repo, commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer getter.Close()
|
||||
|
||||
return getter.Get(path)
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob's size.
|
||||
func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
|
||||
blob := info.Digest.Hex()
|
||||
|
||||
// Ensure s.compressed is initialized. It is build by LayerInfosForCopy.
|
||||
if s.compressed == nil {
|
||||
_, err := s.LayerInfosForCopy(ctx)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
}
|
||||
compressedBlob, found := s.compressed[info.Digest]
|
||||
if found {
|
||||
blob = compressedBlob.Hex()
|
||||
}
|
||||
branch := fmt.Sprintf("ociimage/%s", blob)
|
||||
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
layerSize, err := s.getLayerSize(blob)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
tarsplit, err := s.getTarSplitData(blob)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// if tarsplit is nil we are looking at the manifest. Return directly the file in /content
|
||||
if tarsplit == nil {
|
||||
file, err := s.readSingleFile(branch, "/content")
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return file, layerSize, nil
|
||||
}
|
||||
|
||||
mf := bytes.NewReader(tarsplit)
|
||||
mfz, err := gzip.NewReader(mf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defer mfz.Close()
|
||||
metaUnpacker := storage.NewJSONUnpacker(mfz)
|
||||
|
||||
getter, err := newOSTreePathFileGetter(s.repo, branch)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
ots := asm.NewOutputTarStream(getter, metaUnpacker)
|
||||
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
go func() {
|
||||
io.Copy(pipeWriter, ots)
|
||||
pipeWriter.Close()
|
||||
}()
|
||||
|
||||
rc := ioutils.NewReadCloserWrapper(pipeReader, func() error {
|
||||
getter.Close()
|
||||
return ots.Close()
|
||||
})
|
||||
return rc, layerSize, nil
|
||||
}
|
||||
|
||||
func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, errors.New("manifest lists are not supported by this transport")
|
||||
}
|
||||
lenSignatures, err := s.getLenSignatures()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
branch := fmt.Sprintf("ociimage/%s", s.ref.branchName)
|
||||
|
||||
if s.repo == nil {
|
||||
repo, err := openRepo(s.ref.repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.repo = repo
|
||||
}
|
||||
|
||||
signatures := [][]byte{}
|
||||
for i := int64(1); i <= lenSignatures; i++ {
|
||||
sigReader, err := s.readSingleFile(branch, fmt.Sprintf("/signature-%d", i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sigReader.Close()
|
||||
|
||||
sig, err := ioutil.ReadAll(sigReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signatures = append(signatures, sig)
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of
|
||||
// the image, after they've been decompressed.
|
||||
func (s *ostreeImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
updatedBlobInfos := []types.BlobInfo{}
|
||||
manifestBlob, manifestType, err := s.GetManifest(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
man, err := manifest.FromBlob(manifestBlob, manifestType)
|
||||
|
||||
s.compressed = make(map[digest.Digest]digest.Digest)
|
||||
|
||||
layerBlobs := man.LayerInfos()
|
||||
|
||||
for _, layerBlob := range layerBlobs {
|
||||
branch := fmt.Sprintf("ociimage/%s", layerBlob.Digest.Hex())
|
||||
found, uncompressedDigestStr, err := readMetadata(s.repo, branch, "docker.uncompressed_digest")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found, uncompressedSizeStr, err := readMetadata(s.repo, branch, "docker.uncompressed_size")
|
||||
if err != nil || !found {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uncompressedSize, err := strconv.ParseInt(uncompressedSizeStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uncompressedDigest := digest.Digest(uncompressedDigestStr)
|
||||
blobInfo := types.BlobInfo{
|
||||
Digest: uncompressedDigest,
|
||||
Size: uncompressedSize,
|
||||
MediaType: layerBlob.MediaType,
|
||||
}
|
||||
s.compressed[uncompressedDigest] = layerBlob.Digest
|
||||
updatedBlobInfos = append(updatedBlobInfos, blobInfo)
|
||||
}
|
||||
return updatedBlobInfos, nil
|
||||
}
|
||||
49
vendor/github.com/containers/image/ostree/ostree_transport.go
generated
vendored
49
vendor/github.com/containers/image/ostree/ostree_transport.go
generated
vendored
@@ -4,18 +4,19 @@ package ostree
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/containers/image/directory/explicitfilepath"
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const defaultOSTreeRepo = "/ostree/repo"
|
||||
@@ -66,6 +67,11 @@ type ostreeReference struct {
|
||||
repo string
|
||||
}
|
||||
|
||||
type ostreeImageCloser struct {
|
||||
types.ImageCloser
|
||||
size int64
|
||||
}
|
||||
|
||||
func (t ostreeTransport) ParseReference(ref string) (types.ImageReference, error) {
|
||||
var repo = ""
|
||||
var image = ""
|
||||
@@ -110,7 +116,7 @@ func NewReference(image string, repo string) (types.ImageReference, error) {
|
||||
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
|
||||
// from being ambiguous with values of PolicyConfigurationIdentity.
|
||||
if strings.Contains(resolved, ":") {
|
||||
return nil, errors.Errorf("Invalid OSTreeCI reference %s@%s: path %s contains a colon", image, repo, resolved)
|
||||
return nil, errors.Errorf("Invalid OSTree reference %s@%s: path %s contains a colon", image, repo, resolved)
|
||||
}
|
||||
|
||||
return ostreeReference{
|
||||
@@ -168,35 +174,54 @@ func (ref ostreeReference) PolicyConfigurationNamespaces() []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *ostreeImageCloser) Size() (int64, error) {
|
||||
return s.size, nil
|
||||
}
|
||||
|
||||
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref ostreeReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
return nil, errors.New("Reading ostree: images is currently not supported")
|
||||
func (ref ostreeReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
var tmpDir string
|
||||
if sys == nil || sys.OSTreeTmpDirPath == "" {
|
||||
tmpDir = os.TempDir()
|
||||
} else {
|
||||
tmpDir = sys.OSTreeTmpDirPath
|
||||
}
|
||||
src, err := newImageSource(tmpDir, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return image.FromSource(ctx, sys, src)
|
||||
}
|
||||
|
||||
// NewImageSource returns a types.ImageSource for this reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func (ref ostreeReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
return nil, errors.New("Reading ostree: images is currently not supported")
|
||||
func (ref ostreeReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
var tmpDir string
|
||||
if sys == nil || sys.OSTreeTmpDirPath == "" {
|
||||
tmpDir = os.TempDir()
|
||||
} else {
|
||||
tmpDir = sys.OSTreeTmpDirPath
|
||||
}
|
||||
return newImageSource(tmpDir, ref)
|
||||
}
|
||||
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ostreeReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
func (ref ostreeReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
var tmpDir string
|
||||
if ctx == nil || ctx.OSTreeTmpDirPath == "" {
|
||||
if sys == nil || sys.OSTreeTmpDirPath == "" {
|
||||
tmpDir = os.TempDir()
|
||||
} else {
|
||||
tmpDir = ctx.OSTreeTmpDirPath
|
||||
tmpDir = sys.OSTreeTmpDirPath
|
||||
}
|
||||
return newImageDestination(ref, tmpDir)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
func (ref ostreeReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (ref ostreeReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
return errors.Errorf("Deleting images not implemented for ostree: images")
|
||||
}
|
||||
|
||||
|
||||
41
vendor/github.com/containers/image/pkg/compression/compression.go
generated
vendored
41
vendor/github.com/containers/image/pkg/compression/compression.go
generated
vendored
@@ -5,28 +5,34 @@ import (
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
// DecompressorFunc returns the decompressed stream, given a compressed stream.
|
||||
type DecompressorFunc func(io.Reader) (io.Reader, error)
|
||||
// The caller must call Close() on the decompressed stream (even if the compressed input stream does not need closing!).
|
||||
type DecompressorFunc func(io.Reader) (io.ReadCloser, error)
|
||||
|
||||
// GzipDecompressor is a DecompressorFunc for the gzip compression algorithm.
|
||||
func GzipDecompressor(r io.Reader) (io.Reader, error) {
|
||||
func GzipDecompressor(r io.Reader) (io.ReadCloser, error) {
|
||||
return gzip.NewReader(r)
|
||||
}
|
||||
|
||||
// Bzip2Decompressor is a DecompressorFunc for the bzip2 compression algorithm.
|
||||
func Bzip2Decompressor(r io.Reader) (io.Reader, error) {
|
||||
return bzip2.NewReader(r), nil
|
||||
func Bzip2Decompressor(r io.Reader) (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(bzip2.NewReader(r)), nil
|
||||
}
|
||||
|
||||
// XzDecompressor is a DecompressorFunc for the xz compression algorithm.
|
||||
func XzDecompressor(r io.Reader) (io.Reader, error) {
|
||||
return nil, errors.New("Decompressing xz streams is not supported")
|
||||
func XzDecompressor(r io.Reader) (io.ReadCloser, error) {
|
||||
r, err := xz.NewReader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
|
||||
// compressionAlgos is an internal implementation detail of DetectCompression
|
||||
@@ -65,3 +71,24 @@ func DetectCompression(input io.Reader) (DecompressorFunc, io.Reader, error) {
|
||||
|
||||
return decompressor, io.MultiReader(bytes.NewReader(buffer[:n]), input), nil
|
||||
}
|
||||
|
||||
// AutoDecompress takes a stream and returns an uncompressed version of the
|
||||
// same stream.
|
||||
// The caller must call Close() on the returned stream (even if the input does not need,
|
||||
// or does not even support, closing!).
|
||||
func AutoDecompress(stream io.Reader) (io.ReadCloser, bool, error) {
|
||||
decompressor, stream, err := DetectCompression(stream)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "Error detecting compression")
|
||||
}
|
||||
var res io.ReadCloser
|
||||
if decompressor != nil {
|
||||
res, err = decompressor(stream)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrapf(err, "Error initializing decompression")
|
||||
}
|
||||
} else {
|
||||
res = ioutil.NopCloser(stream)
|
||||
}
|
||||
return res, decompressor != nil, nil
|
||||
}
|
||||
|
||||
110
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
110
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
@@ -7,7 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerAuthConfig struct {
|
||||
@@ -26,24 +26,20 @@ type dockerConfigFile struct {
|
||||
CredHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
defaultPath = "/run/user"
|
||||
authCfg = "containers"
|
||||
authCfgFileName = "auth.json"
|
||||
dockerCfg = ".docker"
|
||||
dockerCfgFileName = "config.json"
|
||||
dockerLegacyCfg = ".dockercfg"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultPerUIDPathFormat = filepath.FromSlash("/run/containers/%d/auth.json")
|
||||
xdgRuntimeDirPath = filepath.FromSlash("containers/auth.json")
|
||||
dockerHomePath = filepath.FromSlash(".docker/config.json")
|
||||
dockerLegacyHomePath = ".dockercfg"
|
||||
|
||||
// ErrNotLoggedIn is returned for users not logged into a registry
|
||||
// that they are trying to logout of
|
||||
ErrNotLoggedIn = errors.New("not logged in")
|
||||
)
|
||||
|
||||
// SetAuthentication stores the username and password in the auth.json file
|
||||
func SetAuthentication(ctx *types.SystemContext, registry, username, password string) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
func SetAuthentication(sys *types.SystemContext, registry, username, password string) error {
|
||||
return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) {
|
||||
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||
return false, setAuthToCredHelper(ch, registry, username, password)
|
||||
}
|
||||
@@ -58,13 +54,23 @@ func SetAuthentication(ctx *types.SystemContext, registry, username, password st
|
||||
// GetAuthentication returns the registry credentials stored in
|
||||
// either auth.json file or .docker/config.json
|
||||
// If an entry is not found empty strings are returned for the username and password
|
||||
func GetAuthentication(ctx *types.SystemContext, registry string) (string, string, error) {
|
||||
if ctx != nil && ctx.DockerAuthConfig != nil {
|
||||
return ctx.DockerAuthConfig.Username, ctx.DockerAuthConfig.Password, nil
|
||||
func GetAuthentication(sys *types.SystemContext, registry string) (string, string, error) {
|
||||
if sys != nil && sys.DockerAuthConfig != nil {
|
||||
return sys.DockerAuthConfig.Username, sys.DockerAuthConfig.Password, nil
|
||||
}
|
||||
|
||||
dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyCfg)
|
||||
paths := [3]string{getPathToAuth(ctx), filepath.Join(homedir.Get(), dockerCfg, dockerCfgFileName), dockerLegacyPath}
|
||||
dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyHomePath)
|
||||
var paths []string
|
||||
pathToAuth, err := getPathToAuth(sys)
|
||||
if err == nil {
|
||||
paths = append(paths, pathToAuth)
|
||||
} else {
|
||||
// Error means that the path set for XDG_RUNTIME_DIR does not exist
|
||||
// but we don't want to completely fail in the case that the user is pulling a public image
|
||||
// Logging the error as a warning instead and moving on to pulling the image
|
||||
logrus.Warnf("%v: Trying to pull image in the event that it is a public image.", err)
|
||||
}
|
||||
paths = append(paths, filepath.Join(homedir.Get(), dockerHomePath), dockerLegacyPath)
|
||||
|
||||
for _, path := range paths {
|
||||
legacyFormat := path == dockerLegacyPath
|
||||
@@ -82,18 +88,21 @@ func GetAuthentication(ctx *types.SystemContext, registry string) (string, strin
|
||||
// GetUserLoggedIn returns the username logged in to registry from either
|
||||
// auth.json or XDG_RUNTIME_DIR
|
||||
// Used to tell the user if someone is logged in to the registry when logging in
|
||||
func GetUserLoggedIn(ctx *types.SystemContext, registry string) string {
|
||||
path := getPathToAuth(ctx)
|
||||
func GetUserLoggedIn(sys *types.SystemContext, registry string) (string, error) {
|
||||
path, err := getPathToAuth(sys)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
username, _, _ := findAuthentication(registry, path, false)
|
||||
if username != "" {
|
||||
return username
|
||||
return username, nil
|
||||
}
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// RemoveAuthentication deletes the credentials stored in auth.json
|
||||
func RemoveAuthentication(ctx *types.SystemContext, registry string) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
func RemoveAuthentication(sys *types.SystemContext, registry string) error {
|
||||
return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) {
|
||||
// First try cred helpers.
|
||||
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||
return false, deleteAuthFromCredHelper(ch, registry)
|
||||
@@ -111,8 +120,8 @@ func RemoveAuthentication(ctx *types.SystemContext, registry string) error {
|
||||
}
|
||||
|
||||
// RemoveAllAuthentication deletes all the credentials stored in auth.json
|
||||
func RemoveAllAuthentication(ctx *types.SystemContext) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
func RemoveAllAuthentication(sys *types.SystemContext) error {
|
||||
return modifyJSON(sys, func(auths *dockerConfigFile) (bool, error) {
|
||||
auths.CredHelpers = make(map[string]string)
|
||||
auths.AuthConfigs = make(map[string]dockerAuthConfig)
|
||||
return true, nil
|
||||
@@ -121,22 +130,32 @@ func RemoveAllAuthentication(ctx *types.SystemContext) error {
|
||||
|
||||
// getPath gets the path of the auth.json file
|
||||
// The path can be overriden by the user if the overwrite-path flag is set
|
||||
// If the flag is not set and XDG_RUNTIME_DIR is ser, the auth.json file is saved in XDG_RUNTIME_DIR/containers
|
||||
// Otherwise, the auth.json file is stored in /run/user/UID/containers
|
||||
func getPathToAuth(ctx *types.SystemContext) string {
|
||||
if ctx != nil {
|
||||
if ctx.AuthFilePath != "" {
|
||||
return ctx.AuthFilePath
|
||||
// If the flag is not set and XDG_RUNTIME_DIR is set, the auth.json file is saved in XDG_RUNTIME_DIR/containers
|
||||
// Otherwise, the auth.json file is stored in /run/containers/UID
|
||||
func getPathToAuth(sys *types.SystemContext) (string, error) {
|
||||
if sys != nil {
|
||||
if sys.AuthFilePath != "" {
|
||||
return sys.AuthFilePath, nil
|
||||
}
|
||||
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(ctx.RootForImplicitAbsolutePaths, defaultPath, strconv.Itoa(os.Getuid()), authCfg, authCfgFileName)
|
||||
if sys.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), nil
|
||||
}
|
||||
}
|
||||
|
||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if runtimeDir == "" {
|
||||
runtimeDir = filepath.Join(defaultPath, strconv.Itoa(os.Getuid()))
|
||||
if runtimeDir != "" {
|
||||
// This function does not in general need to separately check that the returned path exists; that’s racy, and callers will fail accessing the file anyway.
|
||||
// We are checking for os.IsNotExist here only to give the user better guidance what to do in this special case.
|
||||
_, err := os.Stat(runtimeDir)
|
||||
if os.IsNotExist(err) {
|
||||
// This means the user set the XDG_RUNTIME_DIR variable and either forgot to create the directory
|
||||
// or made a typo while setting the environment variable,
|
||||
// so return an error referring to $XDG_RUNTIME_DIR instead of xdgRuntimeDirPath inside.
|
||||
return "", errors.Wrapf(err, "%q directory set by $XDG_RUNTIME_DIR does not exist. Either create the directory or unset $XDG_RUNTIME_DIR.", runtimeDir)
|
||||
} // else ignore err and let the caller fail accessing xdgRuntimeDirPath.
|
||||
return filepath.Join(runtimeDir, xdgRuntimeDirPath), nil
|
||||
}
|
||||
return filepath.Join(runtimeDir, authCfg, authCfgFileName)
|
||||
return fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid()), nil
|
||||
}
|
||||
|
||||
// readJSONFile unmarshals the authentications stored in the auth.json file and returns it
|
||||
@@ -146,9 +165,12 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
|
||||
var auths dockerConfigFile
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
auths.AuthConfigs = map[string]dockerAuthConfig{}
|
||||
return auths, nil
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
auths.AuthConfigs = map[string]dockerAuthConfig{}
|
||||
return auths, nil
|
||||
}
|
||||
return dockerConfigFile{}, err
|
||||
}
|
||||
|
||||
if legacyFormat {
|
||||
@@ -166,11 +188,15 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
|
||||
}
|
||||
|
||||
// modifyJSON writes to auth.json if the dockerConfigFile has been updated
|
||||
func modifyJSON(ctx *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error {
|
||||
path := getPathToAuth(ctx)
|
||||
func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error {
|
||||
path, err := getPathToAuth(sys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.Mkdir(dir, 0700); err != nil {
|
||||
if err = os.MkdirAll(dir, 0700); err != nil {
|
||||
return errors.Wrapf(err, "error creating directory %q", dir)
|
||||
}
|
||||
}
|
||||
|
||||
20
vendor/github.com/containers/image/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
20
vendor/github.com/containers/image/pkg/tlsclientconfig/tlsclientconfig.go
generated
vendored
@@ -34,16 +34,26 @@ func SetupCertificates(dir string, tlsc *tls.Config) error {
|
||||
for _, f := range fs {
|
||||
fullPath := filepath.Join(dir, f.Name())
|
||||
if strings.HasSuffix(f.Name(), ".crt") {
|
||||
systemPool, err := tlsconfig.SystemCertPool()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get system cert pool")
|
||||
}
|
||||
tlsc.RootCAs = systemPool
|
||||
logrus.Debugf(" crt: %s", fullPath)
|
||||
data, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Dangling symbolic link?
|
||||
// Race with someone who deleted the
|
||||
// file after we read the directory's
|
||||
// list of contents?
|
||||
logrus.Warnf("error reading certificate %q: %v", fullPath, err)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if tlsc.RootCAs == nil {
|
||||
systemPool, err := tlsconfig.SystemCertPool()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get system cert pool")
|
||||
}
|
||||
tlsc.RootCAs = systemPool
|
||||
}
|
||||
tlsc.RootCAs.AppendCertsFromPEM(data)
|
||||
}
|
||||
if strings.HasSuffix(f.Name(), ".cert") {
|
||||
|
||||
18
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
18
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
@@ -44,21 +44,21 @@ func (err InvalidPolicyFormatError) Error() string {
|
||||
// DefaultPolicy returns the default policy of the system.
|
||||
// Most applications should be using this method to get the policy configured
|
||||
// by the system administrator.
|
||||
// ctx should usually be nil, can be set to override the default.
|
||||
// sys should usually be nil, can be set to override the default.
|
||||
// NOTE: When this function returns an error, report it to the user and abort.
|
||||
// DO NOT hard-code fallback policies in your application.
|
||||
func DefaultPolicy(ctx *types.SystemContext) (*Policy, error) {
|
||||
return NewPolicyFromFile(defaultPolicyPath(ctx))
|
||||
func DefaultPolicy(sys *types.SystemContext) (*Policy, error) {
|
||||
return NewPolicyFromFile(defaultPolicyPath(sys))
|
||||
}
|
||||
|
||||
// defaultPolicyPath returns a path to the default policy of the system.
|
||||
func defaultPolicyPath(ctx *types.SystemContext) string {
|
||||
if ctx != nil {
|
||||
if ctx.SignaturePolicyPath != "" {
|
||||
return ctx.SignaturePolicyPath
|
||||
func defaultPolicyPath(sys *types.SystemContext) string {
|
||||
if sys != nil {
|
||||
if sys.SignaturePolicyPath != "" {
|
||||
return sys.SignaturePolicyPath
|
||||
}
|
||||
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(ctx.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
|
||||
if sys.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
|
||||
}
|
||||
}
|
||||
return systemDefaultPolicyPath
|
||||
|
||||
14
vendor/github.com/containers/image/signature/policy_eval.go
generated
vendored
14
vendor/github.com/containers/image/signature/policy_eval.go
generated
vendored
@@ -55,14 +55,14 @@ type PolicyRequirement interface {
|
||||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error)
|
||||
isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error)
|
||||
|
||||
// isRunningImageAllowed returns true if the requirement allows running an image.
|
||||
// If it returns false, err must be non-nil, and should be an PolicyRequirementError if evaluation
|
||||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
isRunningImageAllowed(image types.UnparsedImage) (bool, error)
|
||||
isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error)
|
||||
}
|
||||
|
||||
// PolicyReferenceMatch specifies a set of image identities accepted in PolicyRequirement.
|
||||
@@ -175,7 +175,7 @@ func (pc *PolicyContext) requirementsForImageRef(ref types.ImageReference) Polic
|
||||
// a container based on this image; use IsRunningImageAllowed instead.
|
||||
// - Just because a signature is accepted does not automatically mean the contents of the
|
||||
// signature are authorized to run code as root, or to affect system or cluster configuration.
|
||||
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.UnparsedImage) (sigs []*Signature, finalErr error) {
|
||||
func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(ctx context.Context, image types.UnparsedImage) (sigs []*Signature, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -191,7 +191,7 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.UnparsedIma
|
||||
|
||||
// FIXME: rename Signatures to UnverifiedSignatures
|
||||
// FIXME: pass context.Context
|
||||
unverifiedSignatures, err := image.Signatures(context.TODO())
|
||||
unverifiedSignatures, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.UnparsedIma
|
||||
for reqNumber, req := range reqs {
|
||||
// FIXME: Log the requirement itself? For now, we use just the number.
|
||||
// FIXME: supply state
|
||||
switch res, as, err := req.isSignatureAuthorAccepted(image, sig); res {
|
||||
switch res, as, err := req.isSignatureAuthorAccepted(ctx, image, sig); res {
|
||||
case sarAccepted:
|
||||
if as == nil { // Coverage: this should never happen
|
||||
logrus.Debugf(" Requirement %d: internal inconsistency: sarAccepted but no parsed contents", reqNumber)
|
||||
@@ -256,7 +256,7 @@ func (pc *PolicyContext) GetSignaturesWithAcceptedAuthor(image types.UnparsedIma
|
||||
// succeeded but the result was rejection.
|
||||
// WARNING: This validates signatures and the manifest, but does not download or validate the
|
||||
// layers. Users must validate that the layers match their expected digests.
|
||||
func (pc *PolicyContext) IsRunningImageAllowed(image types.UnparsedImage) (res bool, finalErr error) {
|
||||
func (pc *PolicyContext) IsRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (res bool, finalErr error) {
|
||||
if err := pc.changeState(pcReady, pcInUse); err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func (pc *PolicyContext) IsRunningImageAllowed(image types.UnparsedImage) (res b
|
||||
|
||||
for reqNumber, req := range reqs {
|
||||
// FIXME: supply state
|
||||
allowed, err := req.isRunningImageAllowed(image)
|
||||
allowed, err := req.isRunningImageAllowed(ctx, image)
|
||||
if !allowed {
|
||||
logrus.Debugf("Requirement %d: denied, done", reqNumber)
|
||||
return false, err
|
||||
|
||||
6
vendor/github.com/containers/image/signature/policy_eval_baselayer.go
generated
vendored
6
vendor/github.com/containers/image/signature/policy_eval_baselayer.go
generated
vendored
@@ -3,15 +3,17 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prSignedBaseLayer) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBaseLayer) isRunningImageAllowed(image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prSignedBaseLayer) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
// FIXME? Reject this at policy parsing time already?
|
||||
logrus.Errorf("signedBaseLayer not implemented yet!")
|
||||
return false, PolicyRequirementError("signedBaseLayer not implemented yet!")
|
||||
|
||||
10
vendor/github.com/containers/image/signature/policy_eval_signedby.go
generated
vendored
10
vendor/github.com/containers/image/signature/policy_eval_signedby.go
generated
vendored
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
switch pr.KeyType {
|
||||
case SBKeyTypeGPGKeys:
|
||||
case SBKeyTypeSignedByGPGKeys, SBKeyTypeX509Certificates, SBKeyTypeSignedByX509CAs:
|
||||
@@ -69,7 +69,7 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig [
|
||||
return nil
|
||||
},
|
||||
validateSignedDockerManifestDigest: func(digest digest.Digest) error {
|
||||
m, _, err := image.Manifest()
|
||||
m, _, err := image.Manifest(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,16 +90,16 @@ func (pr *prSignedBy) isSignatureAuthorAccepted(image types.UnparsedImage, sig [
|
||||
return sarAccepted, signature, nil
|
||||
}
|
||||
|
||||
func (pr *prSignedBy) isRunningImageAllowed(image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prSignedBy) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
// FIXME: pass context.Context
|
||||
sigs, err := image.Signatures(context.TODO())
|
||||
sigs, err := image.Signatures(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var rejections []error
|
||||
for _, s := range sigs {
|
||||
var reason error
|
||||
switch res, _, err := pr.isSignatureAuthorAccepted(image, s); res {
|
||||
switch res, _, err := pr.isSignatureAuthorAccepted(ctx, image, s); res {
|
||||
case sarAccepted:
|
||||
// One accepted signature is enough.
|
||||
return true, nil
|
||||
|
||||
9
vendor/github.com/containers/image/signature/policy_eval_simple.go
generated
vendored
9
vendor/github.com/containers/image/signature/policy_eval_simple.go
generated
vendored
@@ -3,26 +3,27 @@
|
||||
package signature
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
// prInsecureAcceptAnything semantics: Every image is allowed to run,
|
||||
// but this does not consider the signature as verified.
|
||||
return sarUnknown, nil, nil
|
||||
}
|
||||
|
||||
func (pr *prInsecureAcceptAnything) isRunningImageAllowed(image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prInsecureAcceptAnything) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (pr *prReject) isSignatureAuthorAccepted(image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
func (pr *prReject) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) {
|
||||
return sarRejected, nil, PolicyRequirementError(fmt.Sprintf("Any signatures for image %s are rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
|
||||
func (pr *prReject) isRunningImageAllowed(image types.UnparsedImage) (bool, error) {
|
||||
func (pr *prReject) isRunningImageAllowed(ctx context.Context, image types.UnparsedImage) (bool, error) {
|
||||
return false, PolicyRequirementError(fmt.Sprintf("Running image %s is rejected by policy.", transports.ImageName(image.Reference())))
|
||||
}
|
||||
|
||||
1016
vendor/github.com/containers/image/storage/storage_image.go
generated
vendored
1016
vendor/github.com/containers/image/storage/storage_image.go
generated
vendored
File diff suppressed because it is too large
Load Diff
125
vendor/github.com/containers/image/storage/storage_reference.go
generated
vendored
125
vendor/github.com/containers/image/storage/storage_reference.go
generated
vendored
@@ -3,6 +3,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
@@ -15,57 +16,88 @@ import (
|
||||
// A storageReference holds an arbitrary name and/or an ID, which is a 32-byte
|
||||
// value hex-encoded into a 64-character string, and a reference to a Store
|
||||
// where an image is, or would be, kept.
|
||||
// Either "named" or "id" must be set.
|
||||
type storageReference struct {
|
||||
transport storageTransport
|
||||
reference string
|
||||
named reference.Named // may include a tag and/or a digest
|
||||
id string
|
||||
name reference.Named
|
||||
}
|
||||
|
||||
func newReference(transport storageTransport, reference, id string, name reference.Named) *storageReference {
|
||||
func newReference(transport storageTransport, named reference.Named, id string) (*storageReference, error) {
|
||||
if named == nil && id == "" {
|
||||
return nil, ErrInvalidReference
|
||||
}
|
||||
// We take a copy of the transport, which contains a pointer to the
|
||||
// store that it used for resolving this reference, so that the
|
||||
// transport that we'll return from Transport() won't be affected by
|
||||
// further calls to the original transport's SetStore() method.
|
||||
return &storageReference{
|
||||
transport: transport,
|
||||
reference: reference,
|
||||
named: named,
|
||||
id: id,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// imageMatchesRepo returns true iff image.Names contains an element with the same repo as ref
|
||||
func imageMatchesRepo(image *storage.Image, ref reference.Named) bool {
|
||||
repo := ref.Name()
|
||||
for _, name := range image.Names {
|
||||
if named, err := reference.ParseNormalizedNamed(name); err == nil {
|
||||
if named.Name() == repo {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Resolve the reference's name to an image ID in the store, if there's already
|
||||
// one present with the same name or ID, and return the image.
|
||||
func (s *storageReference) resolveImage() (*storage.Image, error) {
|
||||
var loadedImage *storage.Image
|
||||
if s.id == "" {
|
||||
image, err := s.transport.store.Image(s.reference)
|
||||
// Look for an image that has the expanded reference name as an explicit Name value.
|
||||
image, err := s.transport.store.Image(s.named.String())
|
||||
if image != nil && err == nil {
|
||||
loadedImage = image
|
||||
s.id = image.ID
|
||||
}
|
||||
}
|
||||
if s.id == "" {
|
||||
logrus.Errorf("reference %q does not resolve to an image ID", s.StringWithinTransport())
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
img, err := s.transport.store.Image(s.id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image %q", s.id)
|
||||
}
|
||||
if s.reference != "" {
|
||||
nameMatch := false
|
||||
for _, name := range img.Names {
|
||||
if name == s.reference {
|
||||
nameMatch = true
|
||||
break
|
||||
if s.id == "" && s.named != nil {
|
||||
if digested, ok := s.named.(reference.Digested); ok {
|
||||
// Look for an image with the specified digest that has the same name,
|
||||
// though possibly with a different tag or digest, as a Name value, so
|
||||
// that the canonical reference can be implicitly resolved to the image.
|
||||
images, err := s.transport.store.ImagesByDigest(digested.Digest())
|
||||
if images != nil && err == nil {
|
||||
for _, image := range images {
|
||||
if imageMatchesRepo(image, s.named) {
|
||||
loadedImage = image
|
||||
s.id = image.ID
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !nameMatch {
|
||||
}
|
||||
if s.id == "" {
|
||||
logrus.Debugf("reference %q does not resolve to an image ID", s.StringWithinTransport())
|
||||
return nil, errors.Wrapf(ErrNoSuchImage, "reference %q does not resolve to an image ID", s.StringWithinTransport())
|
||||
}
|
||||
if loadedImage == nil {
|
||||
img, err := s.transport.store.Image(s.id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image %q", s.id)
|
||||
}
|
||||
loadedImage = img
|
||||
}
|
||||
if s.named != nil {
|
||||
if !imageMatchesRepo(loadedImage, s.named) {
|
||||
logrus.Errorf("no image matching reference %q found", s.StringWithinTransport())
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
}
|
||||
return img, nil
|
||||
return loadedImage, nil
|
||||
}
|
||||
|
||||
// Return a Transport object that defaults to using the same store that we used
|
||||
@@ -78,9 +110,9 @@ func (s storageReference) Transport() types.ImageTransport {
|
||||
}
|
||||
}
|
||||
|
||||
// Return a name with a tag, if we have a name to base them on.
|
||||
// Return a name with a tag or digest, if we have either, else return it bare.
|
||||
func (s storageReference) DockerReference() reference.Named {
|
||||
return s.name
|
||||
return s.named
|
||||
}
|
||||
|
||||
// Return a name with a tag, prefixed with the graph root and driver name, to
|
||||
@@ -92,25 +124,25 @@ func (s storageReference) StringWithinTransport() string {
|
||||
if len(options) > 0 {
|
||||
optionsList = ":" + strings.Join(options, ",")
|
||||
}
|
||||
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "+" + s.transport.store.RunRoot() + optionsList + "]"
|
||||
if s.name == nil {
|
||||
return storeSpec + "@" + s.id
|
||||
res := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "+" + s.transport.store.RunRoot() + optionsList + "]"
|
||||
if s.named != nil {
|
||||
res = res + s.named.String()
|
||||
}
|
||||
if s.id == "" {
|
||||
return storeSpec + s.reference
|
||||
if s.id != "" {
|
||||
res = res + "@" + s.id
|
||||
}
|
||||
return storeSpec + s.reference + "@" + s.id
|
||||
return res
|
||||
}
|
||||
|
||||
func (s storageReference) PolicyConfigurationIdentity() string {
|
||||
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
|
||||
if s.name == nil {
|
||||
return storeSpec + "@" + s.id
|
||||
res := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
|
||||
if s.named != nil {
|
||||
res = res + s.named.String()
|
||||
}
|
||||
if s.id == "" {
|
||||
return storeSpec + s.reference
|
||||
if s.id != "" {
|
||||
res = res + "@" + s.id
|
||||
}
|
||||
return storeSpec + s.reference + "@" + s.id
|
||||
return res
|
||||
}
|
||||
|
||||
// Also accept policy that's tied to the combination of the graph root and
|
||||
@@ -121,12 +153,17 @@ func (s storageReference) PolicyConfigurationNamespaces() []string {
|
||||
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
|
||||
driverlessStoreSpec := "[" + s.transport.store.GraphRoot() + "]"
|
||||
namespaces := []string{}
|
||||
if s.name != nil {
|
||||
if s.named != nil {
|
||||
if s.id != "" {
|
||||
// The reference without the ID is also a valid namespace.
|
||||
namespaces = append(namespaces, storeSpec+s.reference)
|
||||
namespaces = append(namespaces, storeSpec+s.named.String())
|
||||
}
|
||||
components := strings.Split(s.name.Name(), "/")
|
||||
tagged, isTagged := s.named.(reference.Tagged)
|
||||
_, isDigested := s.named.(reference.Digested)
|
||||
if isTagged && isDigested { // s.named is "name:tag@digest"; add a "name:tag" parent namespace.
|
||||
namespaces = append(namespaces, storeSpec+s.named.Name()+":"+tagged.Tag())
|
||||
}
|
||||
components := strings.Split(s.named.Name(), "/")
|
||||
for len(components) > 0 {
|
||||
namespaces = append(namespaces, storeSpec+strings.Join(components, "/"))
|
||||
components = components[:len(components)-1]
|
||||
@@ -142,11 +179,11 @@ func (s storageReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (s storageReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
return newImage(ctx, s)
|
||||
func (s storageReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
return newImage(ctx, sys, s)
|
||||
}
|
||||
|
||||
func (s storageReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (s storageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
img, err := s.resolveImage()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,10 +198,10 @@ func (s storageReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s storageReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
func (s storageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
return newImageSource(s)
|
||||
}
|
||||
|
||||
func (s storageReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
func (s storageReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(s)
|
||||
}
|
||||
|
||||
185
vendor/github.com/containers/image/storage/storage_transport.go
generated
vendored
185
vendor/github.com/containers/image/storage/storage_transport.go
generated
vendored
@@ -3,6 +3,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -13,11 +14,14 @@ import (
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ddigest "github.com/opencontainers/go-digest"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
minimumTruncatedIDLength = 3
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
@@ -102,70 +106,74 @@ func (s *storageTransport) DefaultGIDMap() []idtools.IDMap {
|
||||
// ParseStoreReference takes a name or an ID, tries to figure out which it is
|
||||
// relative to the given store, and returns it in a reference object.
|
||||
func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) {
|
||||
var name reference.Named
|
||||
var sum digest.Digest
|
||||
var err error
|
||||
if ref == "" {
|
||||
return nil, ErrInvalidReference
|
||||
return nil, errors.Wrapf(ErrInvalidReference, "%q is an empty reference")
|
||||
}
|
||||
if ref[0] == '[' {
|
||||
// Ignore the store specifier.
|
||||
closeIndex := strings.IndexRune(ref, ']')
|
||||
if closeIndex < 1 {
|
||||
return nil, ErrInvalidReference
|
||||
return nil, errors.Wrapf(ErrInvalidReference, "store specifier in %q did not end", ref)
|
||||
}
|
||||
ref = ref[closeIndex+1:]
|
||||
}
|
||||
refInfo := strings.SplitN(ref, "@", 2)
|
||||
if len(refInfo) == 1 {
|
||||
// A name.
|
||||
name, err = reference.ParseNormalizedNamed(refInfo[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if len(refInfo) == 2 {
|
||||
// An ID, possibly preceded by a name.
|
||||
if refInfo[0] != "" {
|
||||
name, err = reference.ParseNormalizedNamed(refInfo[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
sum, err = digest.Parse(refInfo[1])
|
||||
if err != nil || sum.Validate() != nil {
|
||||
sum, err = digest.Parse("sha256:" + refInfo[1])
|
||||
if err != nil || sum.Validate() != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else { // Coverage: len(refInfo) is always 1 or 2
|
||||
// Anything else: store specified in a form we don't
|
||||
// recognize.
|
||||
return nil, ErrInvalidReference
|
||||
}
|
||||
optionsList := ""
|
||||
options := store.GraphOptions()
|
||||
if len(options) > 0 {
|
||||
optionsList = ":" + strings.Join(options, ",")
|
||||
}
|
||||
storeSpec := "[" + store.GraphDriverName() + "@" + store.GraphRoot() + "+" + store.RunRoot() + optionsList + "]"
|
||||
|
||||
// The reference may end with an image ID. Image IDs and digests use the same "@" separator;
|
||||
// here we only peel away an image ID, and leave digests alone.
|
||||
split := strings.LastIndex(ref, "@")
|
||||
id := ""
|
||||
if sum.Validate() == nil {
|
||||
id = sum.Hex()
|
||||
if split != -1 {
|
||||
possibleID := ref[split+1:]
|
||||
if possibleID == "" {
|
||||
return nil, errors.Wrapf(ErrInvalidReference, "empty trailing digest or ID in %q", ref)
|
||||
}
|
||||
// If it looks like a digest, leave it alone for now.
|
||||
if _, err := digest.Parse(possibleID); err != nil {
|
||||
// Otherwise…
|
||||
if idSum, err := digest.Parse("sha256:" + possibleID); err == nil && idSum.Validate() == nil {
|
||||
id = possibleID // … it is a full ID
|
||||
} else if img, err := store.Image(possibleID); err == nil && img != nil && len(possibleID) >= minimumTruncatedIDLength && strings.HasPrefix(img.ID, possibleID) {
|
||||
// … it is a truncated version of the ID of an image that's present in local storage,
|
||||
// so we might as well use the expanded value.
|
||||
id = img.ID
|
||||
} else {
|
||||
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like an image ID or digest", possibleID)
|
||||
}
|
||||
// We have recognized an image ID; peel it off.
|
||||
ref = ref[:split]
|
||||
}
|
||||
}
|
||||
refname := ""
|
||||
if name != nil {
|
||||
name = reference.TagNameOnly(name)
|
||||
refname = verboseName(name)
|
||||
|
||||
// If we only have one @-delimited portion, then _maybe_ it's a truncated image ID. Only check on that if it's
|
||||
// at least of what we guess is a reasonable minimum length, because we don't want a really short value
|
||||
// like "a" matching an image by ID prefix when the input was actually meant to specify an image name.
|
||||
if id == "" && len(ref) >= minimumTruncatedIDLength && !strings.ContainsAny(ref, "@:") {
|
||||
if img, err := store.Image(ref); err == nil && img != nil && strings.HasPrefix(img.ID, ref) {
|
||||
// It's a truncated version of the ID of an image that's present in local storage;
|
||||
// we need to expand it.
|
||||
id = img.ID
|
||||
ref = ""
|
||||
}
|
||||
}
|
||||
if refname == "" {
|
||||
logrus.Debugf("parsed reference into %q", storeSpec+"@"+id)
|
||||
} else if id == "" {
|
||||
logrus.Debugf("parsed reference into %q", storeSpec+refname)
|
||||
} else {
|
||||
logrus.Debugf("parsed reference into %q", storeSpec+refname+"@"+id)
|
||||
|
||||
var named reference.Named
|
||||
// Unless we have an un-named "ID" or "@ID" reference (where ID might only have been a prefix), which has been
|
||||
// completely parsed above, the initial portion should be a name, possibly with a tag and/or a digest..
|
||||
if ref != "" {
|
||||
var err error
|
||||
named, err = reference.ParseNormalizedNamed(ref)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing named reference %q", ref)
|
||||
}
|
||||
named = reference.TagNameOnly(named)
|
||||
}
|
||||
return newReference(storageTransport{store: store, defaultUIDMap: s.defaultUIDMap, defaultGIDMap: s.defaultGIDMap}, refname, id, name), nil
|
||||
|
||||
result, err := newReference(storageTransport{store: store, defaultUIDMap: s.defaultUIDMap, defaultGIDMap: s.defaultGIDMap}, named, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debugf("parsed reference into %q", result.StringWithinTransport())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *storageTransport) GetStore() (storage.Store, error) {
|
||||
@@ -184,11 +192,14 @@ func (s *storageTransport) GetStore() (storage.Store, error) {
|
||||
return s.store, nil
|
||||
}
|
||||
|
||||
// ParseReference takes a name and/or an ID ("_name_"/"@_id_"/"_name_@_id_"),
|
||||
// ParseReference takes a name and a tag or digest and/or ID
|
||||
// ("_name_"/"@_id_"/"_name_:_tag_"/"_name_:_tag_@_id_"/"_name_@_digest_"/"_name_@_digest_@_id_"/"_name_:_tag_@_digest_"/"_name_:_tag_@_digest_@_id_"),
|
||||
// possibly prefixed with a store specifier in the form "[_graphroot_]" or
|
||||
// "[_driver_@_graphroot_]" or "[_driver_@_graphroot_+_runroot_]" or
|
||||
// "[_driver_@_graphroot_:_options_]" or "[_driver_@_graphroot_+_runroot_:_options_]",
|
||||
// tries to figure out which it is, and returns it in a reference object.
|
||||
// If _id_ is the ID of an image that's present in local storage, it can be truncated, and
|
||||
// even be specified as if it were a _name_, value.
|
||||
func (s *storageTransport) ParseReference(reference string) (types.ImageReference, error) {
|
||||
var store storage.Store
|
||||
// Check if there's a store location prefix. If there is, then it
|
||||
@@ -267,17 +278,23 @@ func (s *storageTransport) ParseReference(reference string) (types.ImageReferenc
|
||||
|
||||
func (s storageTransport) GetStoreImage(store storage.Store, ref types.ImageReference) (*storage.Image, error) {
|
||||
dref := ref.DockerReference()
|
||||
if dref == nil {
|
||||
if sref, ok := ref.(*storageReference); ok {
|
||||
if sref.id != "" {
|
||||
if img, err := store.Image(sref.id); err == nil {
|
||||
return img, nil
|
||||
}
|
||||
if dref != nil {
|
||||
if img, err := store.Image(dref.String()); err == nil {
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
if sref, ok := ref.(*storageReference); ok {
|
||||
if sref.id != "" {
|
||||
if img, err := store.Image(sref.id); err == nil {
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrInvalidReference
|
||||
tmpRef := *sref
|
||||
if img, err := tmpRef.resolveImage(); err == nil {
|
||||
return img, nil
|
||||
}
|
||||
}
|
||||
return store.Image(verboseName(dref))
|
||||
return nil, storage.ErrImageUnknown
|
||||
}
|
||||
|
||||
func (s *storageTransport) GetImage(ref types.ImageReference) (*storage.Image, error) {
|
||||
@@ -323,35 +340,29 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
if scope == "" {
|
||||
return nil
|
||||
}
|
||||
// But if there is anything left, it has to be a name, with or without
|
||||
// a tag, with or without an ID, since we don't return namespace values
|
||||
// that are just bare IDs.
|
||||
scopeInfo := strings.SplitN(scope, "@", 2)
|
||||
if len(scopeInfo) == 1 && scopeInfo[0] != "" {
|
||||
_, err := reference.ParseNormalizedNamed(scopeInfo[0])
|
||||
if err != nil {
|
||||
|
||||
fields := strings.SplitN(scope, "@", 3)
|
||||
switch len(fields) {
|
||||
case 1: // name only
|
||||
case 2: // name:tag@ID or name[:tag]@digest
|
||||
if _, idErr := digest.Parse("sha256:" + fields[1]); idErr != nil {
|
||||
if _, digestErr := digest.Parse(fields[1]); digestErr != nil {
|
||||
return fmt.Errorf("%v is neither a valid digest(%s) nor a valid ID(%s)", fields[1], digestErr.Error(), idErr.Error())
|
||||
}
|
||||
}
|
||||
case 3: // name[:tag]@digest@ID
|
||||
if _, err := digest.Parse(fields[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" {
|
||||
_, err := reference.ParseNormalizedNamed(scopeInfo[0])
|
||||
if err != nil {
|
||||
if _, err := digest.Parse("sha256:" + fields[2]); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = ddigest.Parse("sha256:" + scopeInfo[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return ErrInvalidReference
|
||||
default: // Coverage: This should never happen
|
||||
return errors.New("Internal error: unexpected number of fields form strings.SplitN")
|
||||
}
|
||||
// As for field[0], if it is non-empty at all:
|
||||
// FIXME? We could be verifying the various character set and length restrictions
|
||||
// from docker/distribution/reference.regexp.go, but other than that there
|
||||
// are few semantically invalid strings.
|
||||
return nil
|
||||
}
|
||||
|
||||
func verboseName(name reference.Named) string {
|
||||
name = reference.TagNameOnly(name)
|
||||
tag := ""
|
||||
if tagged, ok := name.(reference.NamedTagged); ok {
|
||||
tag = ":" + tagged.Tag()
|
||||
}
|
||||
return name.Name() + tag
|
||||
}
|
||||
|
||||
13
vendor/github.com/containers/image/tarball/tarball_reference.go
generated
vendored
13
vendor/github.com/containers/image/tarball/tarball_reference.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package tarball
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -66,12 +67,12 @@ func (r *tarballReference) PolicyConfigurationNamespaces() []string {
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (r *tarballReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := r.NewImageSource(ctx)
|
||||
func (r *tarballReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) {
|
||||
src, err := r.NewImageSource(ctx, sys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := image.FromSource(ctx, src)
|
||||
img, err := image.FromSource(ctx, sys, src)
|
||||
if err != nil {
|
||||
src.Close()
|
||||
return nil, err
|
||||
@@ -79,7 +80,7 @@ func (r *tarballReference) NewImage(ctx *types.SystemContext) (types.ImageCloser
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
func (r *tarballReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error {
|
||||
for _, filename := range r.filenames {
|
||||
if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error removing %q: %v", filename, err)
|
||||
@@ -88,6 +89,6 @@ func (r *tarballReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return nil, fmt.Errorf("destination not implemented yet")
|
||||
func (r *tarballReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) {
|
||||
return nil, fmt.Errorf(`"tarball:" locations can only be read from, not written to`)
|
||||
}
|
||||
|
||||
13
vendor/github.com/containers/image/tarball/tarball_src.go
generated
vendored
13
vendor/github.com/containers/image/tarball/tarball_src.go
generated
vendored
@@ -34,7 +34,7 @@ type tarballImageSource struct {
|
||||
manifest []byte
|
||||
}
|
||||
|
||||
func (r *tarballReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
func (r *tarballReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) {
|
||||
// Gather up the digests, sizes, and date information for all of the files.
|
||||
filenames := []string{}
|
||||
diffIDs := []digest.Digest{}
|
||||
@@ -87,6 +87,7 @@ func (r *tarballReference) NewImageSource(ctx *types.SystemContext) (types.Image
|
||||
layerType = imgspecv1.MediaTypeImageLayer
|
||||
uncompressed = nil
|
||||
}
|
||||
// TODO: This can take quite some time, and should ideally be cancellable using ctx.Done().
|
||||
n, err := io.Copy(ioutil.Discard, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %q: %v", filename, err)
|
||||
@@ -206,7 +207,7 @@ func (is *tarballImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *tarballImageSource) GetBlob(blobinfo types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
func (is *tarballImageSource) GetBlob(ctx context.Context, blobinfo types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
// We should only be asked about things in the manifest. Maybe the configuration blob.
|
||||
if blobinfo.Digest == is.configID {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(is.config)), is.configSize, nil
|
||||
@@ -232,7 +233,7 @@ func (is *tarballImageSource) GetBlob(blobinfo types.BlobInfo) (io.ReadCloser, i
|
||||
// It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (is *tarballImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
func (is *tarballImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, "", fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
|
||||
}
|
||||
@@ -254,7 +255,7 @@ func (is *tarballImageSource) Reference() types.ImageReference {
|
||||
return &is.reference
|
||||
}
|
||||
|
||||
// UpdatedLayerInfos() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (*tarballImageSource) UpdatedLayerInfos() []types.BlobInfo {
|
||||
return nil
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (*tarballImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
1
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
1
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
||||
_ "github.com/containers/image/directory"
|
||||
_ "github.com/containers/image/docker"
|
||||
_ "github.com/containers/image/docker/archive"
|
||||
_ "github.com/containers/image/docker/daemon"
|
||||
_ "github.com/containers/image/oci/archive"
|
||||
_ "github.com/containers/image/oci/layout"
|
||||
_ "github.com/containers/image/openshift"
|
||||
|
||||
8
vendor/github.com/containers/image/transports/alltransports/docker_daemon.go
generated
vendored
Normal file
8
vendor/github.com/containers/image/transports/alltransports/docker_daemon.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !containers_image_docker_daemon_stub
|
||||
|
||||
package alltransports
|
||||
|
||||
import (
|
||||
// Register the docker-daemon transport
|
||||
_ "github.com/containers/image/docker/daemon"
|
||||
)
|
||||
9
vendor/github.com/containers/image/transports/alltransports/docker_daemon_stub.go
generated
vendored
Normal file
9
vendor/github.com/containers/image/transports/alltransports/docker_daemon_stub.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build containers_image_docker_daemon_stub
|
||||
|
||||
package alltransports
|
||||
|
||||
import "github.com/containers/image/transports"
|
||||
|
||||
func init() {
|
||||
transports.Register(transports.NewStubTransport("docker-daemon"))
|
||||
}
|
||||
2
vendor/github.com/containers/image/transports/alltransports/ostree.go
generated
vendored
2
vendor/github.com/containers/image/transports/alltransports/ostree.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// +build !containers_image_ostree_stub
|
||||
// +build !containers_image_ostree_stub,linux
|
||||
|
||||
package alltransports
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user