Compare commits

...

76 Commits

Author SHA1 Message Date
Antonio Murdaca 54e76afc03
Merge pull request #1281 from wking/gofmt-show-diff
Makefile: Use 'git diff' to show gofmt changes
2018-01-24 15:32:49 +01:00
Daniel J Walsh 4fee97abe3
Merge pull request #1278 from remore/patch-1
Fix a few minor errors in the tutorial document
2018-01-24 04:15:33 -05:00
Kei Sawada a50f352eb4 Update tutorial.md to fix a few minor errors
Signed-off-by: Kei Sawada <k@swd.cc>
2018-01-24 14:47:32 +09:00
Mrunal Patel ed40d645cd
Merge pull request #1255 from runcom/panics-grpc-getters
server: use grpc getters to avoid panics
2018-01-23 07:43:08 -08:00
W. Trevor King 8dbc2d1fff Makefile: Use 'git diff' to show gofmt changes
This makes fixing errors easier.  Before this commit, errors looked
like [1]:

  $ make gofmt
  !!! 'gofmt -s' needs to be run on the following files:
  ./lib/config.go
  make: *** [gofmt] Error 1

But that's not very helpful when your local gofmt thinks the file is
fine.  With this commit, errors will look like:

  $ make gofmt
  find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+
  git diff --exit-code
  diff --git a/lib/config.go b/lib/config.go
  index 1acca8c7..6a63b2b0 100644
  --- a/lib/config.go
  +++ b/lib/config.go
  @@ -2,7 +2,7 @@ package lib

   import (
          "bytes"
  -"io/ioutil"
  +       "io/ioutil"

          "github.com/BurntSushi/toml"
          "github.com/kubernetes-incubator/cri-o/oci"
  make: *** [Makefile:68: gofmt] Error 1

(or whatever, I just stuffed in a formatting error for demonstration
purposes).

Also remove the helper script in favor of direct Makefile calls,
because with Git handling difference reporting and exit status, this
becomes a simpler check.  find's -exec, !, and -path arguments are
specified in POSIX [2].

[1]: https://travis-ci.org/kubernetes-incubator/cri-o/jobs/331949394#L1075
[2]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-22 16:47:09 -08:00
Mrunal Patel ddb14b7303
Merge pull request #1269 from wking/server-capabilities-setup-helper
server/container_create: Factor out setupCapabilities helper
2018-01-22 15:29:22 -08:00
Mrunal Patel 924821e4bf
Merge pull request #1277 from wking/namespace-test-helper
test/namespaces: Factor out pid_namespace_test helper
2018-01-20 19:49:38 -08:00
W. Trevor King 080b84dfcd test/namespaces: Factor out pid_namespace_test helper
DRY up this code.  The ${parameter:-word} syntax is in POSIX [1].

[1]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-20 15:58:47 -08:00
Mrunal Patel 214096b7ed
Merge pull request #1268 from wking/libdevmapper-install-check
hack/libdm_installed.sh: Add a test for libdevmapper.h
2018-01-20 09:19:24 -08:00
Antonio Murdaca 8c87b6104f
Merge pull request #1267 from mrunalp/update_readme_1.9
Add 1.9 release to compatibility table
2018-01-20 02:28:20 +01:00
Mrunal Patel b7995aa526
Merge pull request #1275 from wking/remove-unused-play-png
docs: Remove the unused play.png
2018-01-19 17:02:33 -08:00
W. Trevor King 822a6516cf docs: Remove the unused play.png
The last consumer was removed in 1bf6d203 (Remove kpod code after
repository move, 2017-11-02, #1111).

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-19 16:12:39 -08:00
W. Trevor King 523326b7ba server/container_create: Factor out setupCapabilities helper
Having a separate function holding the details of this makes reading
createSandboxContainer easier.

While I was moving the code, I've also cleaned up two things:

* The nil capabilities check is now earlier, where before it had been
  between the ALL handling and the non-ALL handling.

* I've added a capPrefixed variable to avoid having multiple
  toCAPPrefixed calls per capability.

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-19 11:52:45 -08:00
W. Trevor King 826298483a hack/libdm_installed: Add a test for libdevmapper.h
Avoid crashing 'make' with:

  No package 'devmapper' found

by disabling the devmapper driver when the library it requires is not
installed.  Also give the libdm_no_deferred_remove script a more
specific name to avoid confusion.

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-19 11:43:24 -08:00
Mrunal Patel 7851115693 Add 1.9 release to compatibility table
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2018-01-19 10:37:51 -08:00
Antonio Murdaca 77561e95cf
Merge pull request #1264 from wking/test-readme-plugins-moved-from-cni
test/README: Update the CNI plugins instructions for /cni → /plugins
2018-01-18 23:58:08 +01:00
Antonio Murdaca cbfdda868a
Merge pull request #1263 from wking/doc-stale-make-output
kubernetes: Simplify and freshen the required-files table
2018-01-18 23:54:15 +01:00
W. Trevor King 282b900433 test/README: Update the CNI plugins instructions for /cni -> /plugins
Catching up with the Dockerfile change from f51b0a10 (Dockerfile: move
to containernetworking/plugins, 2017-05-25, #536).  The new plugins
commit from f51b0a10 is still the current Dockerfile entry.

This commit also replaces the previous 'go get' call with a git clone
to match the Dockerfile's approach.  I've added an additional 'cd'
call so I don't have to repeat $GOPATH/... more than once, but other
than that, the example matches the current Dockerfile entry.

I've also removed some line-continuation slashes we've been dragging
around since the section landed 07ccda33 (tests: Install CNI
configuration files by default, 2017-04-06, #434).  I'm guessing they
were a copy/paste bug from the Dockerfile, but this example has new
prompts for each command (so it doesn't need continuation) while the
Dockerfile is using && chaining (so it does).

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 14:12:33 -08:00
Mrunal Patel 1bb5846d7d
Merge pull request #1261 from wking/clear-containers-moved
test/README: Clear Containers moved to clearcontainers/runtime
2018-01-18 14:01:42 -08:00
W. Trevor King 15d839ea0d kubernetes: Simplify and freshen the required-files table
The cri-o entries are stale vs. the content currently installed by the
Makefile.  This commit drops them and just references the make call
before starting the table, which lets us stay DRY.

runc is not built from the cri-o repository.  The docs have claimed it
was since 983aec63 (doc: Add instruction to run cri-o with kubernetes,
2017-01-31, #353), but it's independent like the CNI plugins.

The CNI plugins were moved to containernetworking/plugins in
containernetworking/cni@bc0d09e (plugins: moved to
containernetworking/plugins, 2017-05-17, containernetworking/cni#457).

I've added a link to the in-repo policy.json example.  We probably
also want to link to the docs (for the version we vendor?) [1], but
I've left that alone for now.

The CNI config examples were removed from the project README in
9088a12c (contrib: cni: provide example CNI configurations,
2016-12-24, #295).  I've adjusted the reference to point to the new
location, although again, I'd rather replace this with links to
upstream docs.

[1]: 3d0304a021/docs/policy.json.md

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 13:49:50 -08:00
W. Trevor King bf8a99c085 tutorial: Drop 'make install' output to stay DRY
'make install' hasn't installed crio.conf since 8b632729 (Install to
/usr/local to avoid conflicts with vendor binaries, 2017-01-04, #304).
And Make output is usually not particularly interesting.

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 13:27:39 -08:00
W. Trevor King 2bf750c871 tutorial: Drop install.config output to stay DRY
install.config has also installed rio-umount.conf since 51b225474
(Tell oci-umount where to remove mountpoints inside container, #937,
2017-09-21).  And Make output is usually not particularly interesting.

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 13:18:28 -08:00
Mrunal Patel ba2b4a03d0
Merge pull request #1262 from wking/mailmap
.mailmap: Add entries for inconsistent users
2018-01-18 10:55:53 -08:00
W. Trevor King 8c7c70c2db .mailmap: Add entries for inconsistent users
Where the same user had multiple entries, I mostly went with whichever
entry had the most-recent non-merge commits.

The order is alphabetical according to Emacs' sort-lines.

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 10:17:00 -08:00
W. Trevor King e124834b0d test/README: Clear Containers moved to clearcontainers/runtime
And changed the name of their binary.  This commit catches the docs up
with intel/cc-oci-runtime#1065 (merged 2017-09-25).

Signed-off-by: W. Trevor King <wking@tremily.us>
2018-01-18 09:47:56 -08:00
Antonio Murdaca cb8033cd19
Merge pull request #1244 from rhatdan/hooks-args
Allow additional arguments to be passed into hooks
2018-01-15 23:29:45 +01:00
Antonio Murdaca 8c190a683c
server: use grpc getters to avoid panics
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2018-01-12 16:14:29 +01:00
Mrunal Patel d0e0303921
Merge pull request #1252 from runcom/node-e2e
[DO NOT MERGE] contrib: test: add node-e2e job
2018-01-11 10:13:36 -08:00
Antonio Murdaca 8d2a572ead
contrib: test: add node-e2e job
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2018-01-11 16:56:47 +01:00
Daniel J Walsh 22e25158ca
Merge pull request #1251 from vbatts/Makefile_config_target
Makefile: installing a config, requires a config
2018-01-11 06:12:47 -05:00
Vincent Batts 27c2eda635
Makefile: installing a config, requires a config
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2018-01-10 16:23:35 -05:00
Daniel J Walsh 23d20c9db5 Allow additional arguments to be passed into hooks
If a packager wants to be able to support addititional arguments on his
hook this will allow them to setup the configuration with these arguments.

For example this would allow a hook developer to add support for a --debug
flag to change the level of debugging in his hook.

In order to complete this task, I had to vendor in the latest
github.com://opencontainers/runtime-tools, which caused me to have to fix a
Mount and Capability interface calls

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2018-01-09 13:44:16 -05:00
Mrunal Patel 41aaf4e3d8
Merge pull request #1250 from giuseppe/fix-tmpdir-files
syscontainer: create /var/run/crio
2018-01-09 10:01:11 -08:00
Giuseppe Scrivano 2cb22eba49
syscontainer, fedora: create /var/run/crio
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-09 18:01:03 +01:00
Giuseppe Scrivano 6bb1b7e17d
syscontainer, rhel: create /var/run/crio
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-09 18:00:53 +01:00
Giuseppe Scrivano b1b380d67b
syscontainer, centos: create /var/run/crio
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-09 18:00:43 +01:00
Antonio Murdaca 6f4d7c1ae0
Merge pull request #1216 from rhatdan/conmon
Improve error messages on missing runtime
2018-01-06 14:48:22 +01:00
Mrunal Patel c351bc81e1
Merge pull request #1245 from giuseppe/system-container-read-env-from-file
contrib: system containers read env from /etc/sysconfig/crio-(network|storage)
2018-01-04 14:31:43 -08:00
Giuseppe Scrivano b5167d4e8f
syscontainer, centos: read env variables from files
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-04 20:14:52 +01:00
Giuseppe Scrivano 1f75ec82e1
syscontainer, fedora: read env variables from files
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-04 20:14:39 +01:00
Giuseppe Scrivano 3881f375b9
syscontainer, rhel: read env variables from files
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2018-01-04 20:14:29 +01:00
Mrunal Patel ad46c581fa
Merge pull request #1243 from rhatdan/Makefile
Add -i flag to speed up compilation of cri-o packages
2018-01-04 10:54:48 -08:00
Daniel J Walsh 3c1c6d047e Add -i flag to speed up compilation of cri-o packages
Instead of compiling all of the *.go files each time, the
-i flag will cause them to be only compiled if they changed.

This will make developers much happier.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2018-01-04 10:53:33 -05:00
Mrunal Patel a34038350c
Merge pull request #1237 from spiffxp/update-code-of-conduct
Update code-of-conduct.md
2018-01-02 09:38:52 -08:00
Aaron Crickenberger a28eb8374e Update code-of-conduct.md
Refer to kubernetes/community as authoritative source for code of conduct

Signed-off-by: Aaron Crickenberger <spiffxp@gmail.com>
2018-01-02 06:55:21 -08:00
Mrunal Patel 295a11eb17
Merge pull request #1239 from jongwu/enable_arm64
Add bsdmainutils tool to Dockerfile to enable integration test on arm64
2018-01-01 18:04:27 -08:00
Mrunal Patel 28976738de
Merge pull request #1240 from wanghaoran1988/fix_log
fix log
2018-01-01 18:03:30 -08:00
Haoran Wang 88b13dfddf fix log
Signed-off-by: Haoran Wang <haowang@redhat.com>
2017-12-29 14:25:55 +08:00
Jianyong Wu 8b1fefad71 Add bsdmainutils tool to Dockerfile to enable integration test on arm64
Build image for integration test on arm64 will fail for lack of
hexdump. Add bsdmainutils tool to eliminate that failure and let
build image succussfully

Signed-off-by: Jianyong Wu <jianyong.wu@arm.com>
2017-12-28 16:45:56 +08:00
Mrunal Patel 6b91df3da7
Merge pull request #1236 from runcom/cpuset-ctr-create
container_create: set cpuset cpus|mems
2017-12-23 12:20:45 -08:00
Antonio Murdaca de0be63495
container_create: set cpuset cpus|mems
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-20 19:20:57 +01:00
Daniel J Walsh a85f3127d8 Improve error messages on missing runtime
Also stat.h is included twice,
Add more info on log file name and error when failing to open.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2017-12-18 16:46:19 -05:00
Daniel J Walsh 6c0b79b706
Merge pull request #1208 from runcom/moar-tests
contrib: test: integration: enable more e2e kube tests
2017-12-18 10:25:50 -05:00
Mrunal Patel aee7dea272
Merge pull request #1227 from runcom/bump-runc-systemd-race
bump runc to c6e4a1ebeb1a72b529c6f1b6ee2b1ae5b868b14f
2017-12-15 08:56:40 -08:00
Antonio Murdaca e344ad105a
Merge pull request #1116 from nalind/storage-update-2
Update containers/image and containers/storage
2017-12-15 17:01:29 +01:00
Antonio Murdaca 43119a7b13
Merge branch 'lock-free-ops' into moar-tests
* lock-free-ops:
  lib,oci: drop stateLock when possible
2017-12-15 16:46:42 +01:00
Antonio Murdaca ecc572e7cf
lib,oci: drop stateLock when possible
Should fix a possible deadlock in, at least, ListPodSandbox.
There seems to be no reason to hold stateLock when doing operations on
the memory_store for containers and sandboxes.

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-15 15:31:58 +01:00
Antonio Murdaca 455245e65b
bump runc to c6e4a1ebeb1a72b529c6f1b6ee2b1ae5b868b14f
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-15 14:33:44 +01:00
Antonio Murdaca 7d2bde110a
contrib: test: integration: enable more e2e kube tests
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-14 22:21:42 +01:00
Nalin Dahyabhai fa90249c59 Playbooks: install the atomic-registries package
Install atomic-registries to get a /etc/containers/registries.conf file,
so that we can resolve image names that don't include domain portions.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 72442d0957 Don't skip a critest that we now pass
We can pass the "listImage should get exactly 2 repoTags in the result
image" test now, so we no longer need to skip it.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 0ab8c507f4 Install python-rhsm-certificates, handle python-boto
Add python-rhsm-certificates to the list of packages that we require, so
that the required certificates are available for the
pull-image-with-signature tests.

Add per-distribution package install tasks so that we install either
python2-boto or python-boto, depending on whether we're running on
Fedora or RHEL/CentOS, respectively.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 492f758176 Playbooks: don't assume the default network is eth0
Replace instances of "ansible_eth0.ipv4.address" with
"ansible_default_ipv4.address" in the integration test playbook, so that
we can run tests without depending on the name of the primary network
interface being "eth0".

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 893aa4e8c7 Be more diligent about cleaning up failed-to-create containers
If server/Server.createSandboxContainer() fails after calling
server/Server.StorageRuntimeServer().CreateContainer(), cleanup logic in
server/Server.CreateContainer() won't try to clean it up, but we still
need to clean up the on-disk container and its layer.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 6a456d1502 Use crictl instead of crioctl in image integration tests
Use crictl instead of crioctl in some of the integration tests that
exercise image handling.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai 5ea050fc12 Handle truncated IDs in imageService.ResolveNames()
Have ResolveNames() check if the value that it's been given is a
truncated version of the ID of a locally-available image, and if it is,
return the value as it was given.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai ff7bbb4f0d Switch to ImageServer.UntagImage in RemoveImage handler
Add an UntagImage() method to pkg/storage/ImageServer, which will check
if the passed-in NameOrID is a name.  If so, it merely removes that name
from the image, removing the image only if it was the last name that the
image had.  If the NameOrID is an image ID, the image is removed, as
RemoveImage() does.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:53 -05:00
Nalin Dahyabhai f3b7065bd8 Return image references from the storage package
The image's canonical reference is a name with a digest of the image's
manifest, so in imageService.ImageStatus() and
imageService.ListImages(), divide the image's name list into tagged and
digested values, and if we have names, add canonical versions.

In Server.ContainerStatus(), return the image name as it was given to us
as the image, and the image digested reference as the image reference.

In Server.ListImages(), be sure to only return tagged names in the
RepoTags field.  In Server.ImageStatus(), also return canonical
references in the RepoDigests field.

In Server.PullImage(), be sure that we consistently return the same
image reference for an image, whether we ended up pulling it or not.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 14:23:52 -05:00
Nalin Dahyabhai 553979e1fc storage: API fixups
github.com/containers/image/types.ImageReference.NewImage() can take a
*github.com/containers/image/types.SystemContext now, so pass it one if
pkg/storage/imageService.CanPull() has one to give it.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 11:06:55 -05:00
Nalin Dahyabhai 0651d3a8de Update containers/image and containers/storage
Bump containers/image to 3d0304a02154dddc8f97cc833aa0861cea5e9ade, and
containers/storage to 0d32dfce498e06c132c60dac945081bf44c22464.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
2017-12-14 11:06:23 -05:00
Mrunal Patel 2fa1f3f74a
Merge pull request #1221 from runcom/split-critest-from-e2e
CI: split critest from e2e
2017-12-13 16:53:20 -08:00
Antonio Murdaca d91df68638
CI: split critest from e2e
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-14 00:09:36 +01:00
Mrunal Patel da50e6ca11
Merge pull request #1197 from runcom/sys-cont
contrib: import system containers
2017-12-13 09:22:55 -08:00
Mrunal Patel ebc249cad8
Merge pull request #1214 from runcom/fix-1.10-vendor
vendor: bump to kube 1.10/master
2017-12-11 11:14:15 -08:00
Antonio Murdaca f317ffce5b
vendor: bump to kube 1.10/master
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-11 16:45:48 +01:00
Antonio Murdaca 06904d4dbb
contrib: import system containers
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-12-02 12:24:40 +01:00
708 changed files with 66673 additions and 20257 deletions

10
.mailmap Normal file
View File

@ -0,0 +1,10 @@
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
Antonio Murdaca <runcom@redhat.com> <runcom@users.noreply.github.com>
CuiHaozhi <cuihaozhi@chinacloud.com.cn> <cuihz@wise2c.com>
Daniel J Walsh <dwalsh@redhat.com>
Haiyan Meng <hmeng@redhat.com> <haiyanalady@gmail.com>
Lorenzo Fontana <lo@linux.com> <fontanalorenz@gmail.com>
Mrunal Patel <mrunalp@gmail.com> <mpatel@redhat.com>
Mrunal Patel <mrunalp@gmail.com> <mrunal@me.com>
Pengfei Ni <feiskyer@gmail.com> <feiskyer@users.noreply.github.com>
Tobias Klauser <tklauser@distanz.ch> <tobias.klauser@gmail.com>

View File

@ -38,6 +38,7 @@ RUN apt-get update && apt-get install -y \
netcat \
socat \
--no-install-recommends \
bsdmainutils \
&& apt-get clean
# install bats
@ -56,7 +57,7 @@ RUN mkdir -p /usr/src/criu \
&& rm -rf /usr/src/criu
# Install runc
ENV RUNC_COMMIT 84a082bfef6f932de921437815355186db37aeb1
ENV RUNC_COMMIT c6e4a1ebeb1a72b529c6f1b6ee2b1ae5b868b14f
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \

View File

@ -11,7 +11,7 @@ LIBEXECDIR ?= ${PREFIX}/libexec
MANDIR ?= ${PREFIX}/share/man
ETCDIR ?= ${DESTDIR}/etc
ETCDIR_CRIO ?= ${ETCDIR}/crio
BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh)
BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_installed.sh) $(shell hack/libdm_no_deferred_remove_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh)
CRICTL_CONFIG_DIR=${DESTDIR}/etc
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
@ -64,7 +64,8 @@ lint: .gopathok
@./.tool/lint
gofmt:
@./hack/verify-gofmt.sh
find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+
git diff --exit-code
conmon:
$(MAKE) -C $@
@ -73,16 +74,16 @@ pause:
$(MAKE) -C $@
test/bin2img/bin2img: .gopathok $(wildcard test/bin2img/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/bin2img
$(GO) build -i $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/bin2img
test/copyimg/copyimg: .gopathok $(wildcard test/copyimg/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/copyimg
$(GO) build -i $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/copyimg
test/checkseccomp/checkseccomp: .gopathok $(wildcard test/checkseccomp/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/checkseccomp
$(GO) build -i $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/checkseccomp
crio: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/crio $(PROJECT))
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o bin/$@ $(PROJECT)/cmd/crio
$(GO) build -i $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o bin/$@ $(PROJECT)/cmd/crio
crio.conf: crio
./bin/crio --config="" config --default > crio.conf
@ -145,7 +146,7 @@ install.man:
install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES)) -t $(MANDIR)/man5
install ${SELINUXOPT} -m 644 $(filter %.8,$(MANPAGES)) -t $(MANDIR)/man8
install.config:
install.config: crio.conf
install ${SELINUXOPT} -D -m 644 crio.conf $(ETCDIR_CRIO)/crio.conf
install ${SELINUXOPT} -D -m 644 seccomp.json $(ETCDIR_CRIO)/seccomp.json
install ${SELINUXOPT} -D -m 644 crio-umount.conf $(OCIUMOUNTINSTALLDIR)/crio-umount.conf

View File

@ -12,6 +12,7 @@
|----------------------------|-------------------------------|--------------------|
| CRI-O 1.0.x - release-1.0 | Kubernetes 1.7 branch, v1.7.x | = |
| CRI-O 1.8.x - release-1.8 | Kubernetes 1.8 branch, v1.8.x | = |
| CRI-O 1.9.x - release-1.9 | Kubernetes 1.9 branch, v1.9.x | = |
| CRI-O HEAD - master | Kubernetes master branch | ✓ |
Key:

View File

@ -509,7 +509,7 @@ func main() {
if graceful && strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") {
err = nil
} else {
logrus.Errorf("Failed to serve grpc grpc request: %v", err)
logrus.Errorf("Failed to serve grpc request: %v", err)
}
}
}()

View File

@ -1,55 +1,3 @@
## Kubernetes Community Code of Conduct
# Kubernetes Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering
an open and welcoming community, we pledge to respect all people who contribute
through reporting issues, posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery.
* Personal attacks.
* Trolling or insulting/derogatory comments.
* Public or private harassment.
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission.
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are not
aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers
commit themselves to fairly and consistently applying these principles to every aspect
of managing this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a Kubernetes maintainer, Sarah Novotny <sarahnovotny@google.com>, and/or Dan Kohn <dan@linuxfoundation.org>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### Kubernetes Events Code of Conduct
Kubernetes events are working conferences intended for professional networking and collaboration in the
Kubernetes community. Attendees are expected to behave according to professional standards and in accordance
with their employer's policies on appropriate workplace behavior.
While at Kubernetes events or related social networking opportunities, attendees should not engage in
discriminatory or offensive speech or actions regarding gender, sexuality, race, or religion. Speakers should
be especially aware of these concerns.
The Kubernetes team does not condone any statements by speakers contrary to these standards. The Kubernetes
team reserves the right to deny entrance and/or eject from an event (without refund) any individual found to
be engaging in discriminatory or offensive speech or actions.
Please bring any concerns to the immediate attention of the Kubernetes event staff.
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@ -12,7 +12,6 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/eventfd.h>
#include <sys/stat.h>
@ -350,7 +349,7 @@ static int write_k8s_log(int fd, stdpipe_t pipe, const char *buf, ssize_t buflen
/* Open the log path file again */
log_fd = open(opt_log_path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0600);
if (log_fd < 0)
pexit("Failed to open log file");
pexit("Failed to open log file %s: %s", opt_log_path, strerror(errno));
fd = log_fd;
}
@ -1121,6 +1120,8 @@ int main(int argc, char *argv[])
if (opt_runtime_path == NULL)
nexit("Runtime path not provided. Use --runtime");
if (access(opt_runtime_path, X_OK) < 0)
pexit("Runtime path %s is not valid: %s", opt_runtime_path, strerror(errno));
if (!opt_exec && opt_exit_dir == NULL)
nexit("Container exit directory not provided. Use --exit-dir");

View File

@ -0,0 +1,29 @@
FROM centos
ENV VERSION=0 RELEASE=1 ARCH=x86_64
LABEL com.redhat.component="cri-o" \
name="$FGC/cri-o" \
version="$VERSION" \
release="$RELEASE.$DISTTAG" \
architecture="$ARCH" \
usage="atomic install --system --system-package=no crio && systemctl start crio" \
summary="The cri-o daemon as a system container." \
maintainer="Yu Qi Zhang <jzehrarnyg@gmail.com>" \
atomic.type="system"
RUN yum-config-manager --nogpgcheck --add-repo https://cbs.centos.org/repos/virt7-container-common-candidate/x86_64/os/ && \
yum install --disablerepo=extras --nogpgcheck --setopt=tsflags=nodocs -y iptables cri-o socat iproute runc && \
rpm -V iptables cri-o iproute runc && \
yum clean all && \
mkdir -p /exports/hostfs/etc/crio /exports/hostfs/opt/cni/bin/ /exports/hostfs/var/lib/containers/storage/ && \
cp /etc/crio/* /exports/hostfs/etc/crio && \
if test -e /usr/libexec/cni; then cp -Lr /usr/libexec/cni/* /exports/hostfs/opt/cni/bin/; fi
RUN sed -i '/storage_option =/s/.*/&\n"overlay.override_kernel_check=1",/' /exports/hostfs/etc/crio/crio.conf
COPY manifest.json tmpfiles.template config.json.template service.template /exports/
COPY set_mounts.sh /
COPY run.sh /usr/bin/
CMD ["/usr/bin/run.sh"]

View File

@ -0,0 +1,57 @@
# cri-o
This is the cri-o daemon as a system container.
## Building the image from source:
```
# git clone https://github.com/projectatomic/atomic-system-containers
# cd atomic-system-containers/cri-o
# docker build -t crio .
```
## Running the system container, with the atomic CLI:
Pull from registry into ostree:
```
# atomic pull --storage ostree $REGISTRY/crio
```
Or alternatively, pull from local docker:
```
# atomic pull --storage ostree docker:crio:latest
```
Install the container:
Currently we recommend using --system-package=no to avoid having rpmbuild create an rpm file
during installation. This flag will tell the atomic CLI to fall back to copying files to the
host instead.
```
# atomic install --system --system-package=no --name=crio ($REGISTRY)/crio
```
Start as a systemd service:
```
# systemctl start crio
```
Stopping the service
```
# systemctl stop crio
```
Removing the container
```
# atomic uninstall crio
```
## Binary version
You can find the image automatically built as: registry.centos.org/projectatomic/cri-o:latest

View File

@ -0,0 +1,41 @@
# This is for the purpose of building containers on the CentOS Community Container
# Pipeline. The containers are built, tested and delivered to registry.centos.org and
# lifecycled as well. A corresponding entry must exist in the container index itself,
# located at https://github.com/CentOS/container-index/tree/master/index.d
# You can know more at the following links:
# * https://github.com/CentOS/container-pipeline-service/blob/master/README.md
# * https://github.com/CentOS/container-index/blob/master/README.rst
# * https://wiki.centos.org/ContainerPipeline
# This will be part of the name of the container. It should match the job-id in index entry
job-id: cri-o
#the following are optional, can be left blank
#defaults, where applicable are filled in
#nulecule-file : nulecule
# This flag tells the container pipeline to skip user defined tests on their container
test-skip : True
# This is path of the script that initiates the user defined tests. It must be able to
# return an exit code.
test-script : null
# This is the path of custom build script.
build-script : null
# This is the path of the custom delivery script
delivery-script : null
# This flag tells the pipeline to deliver this container to docker hub.
docker-index : True
# This flag can be used to enable or disable the custom delivery
custom-delivery : False
# This flag can be used to enable or disable delivery of container to local registry
local-delivery : True
Upstreams :
- ref :
url :

View File

@ -0,0 +1,427 @@
{
"ociVersion": "1.0.0",
"platform": {
"arch": "amd64",
"os": "linux"
},
"process": {
"args": [
"/usr/bin/run.sh"
],
"capabilities": {
"ambient": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"bounding": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"effective": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"inheritable": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"permitted": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
]
},
"selinuxLabel": "system_u:system_r:container_runtime_t:s0",
"cwd": "/",
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin",
"TERM=xterm",
"LOG_LEVEL=$LOG_LEVEL",
"NAME=$NAME"
],
"noNewPrivileges": false,
"terminal": false,
"user": {
"gid": 0,
"uid": 0
}
},
"root": {
"path": "rootfs",
"readonly": true
},
"hooks": {},
"linux": {
"namespaces": [
{
"type": "mount"
}
],
"resources": {
"devices": [
{
"access": "rwm",
"allow": true
}
]
},
"rootfsPropagation": "private"
},
"mounts": [
{
"destination": "/tmp",
"options": [
"private",
"bind",
"rw",
"mode=755"
],
"source": "/tmp",
"type": "bind"
},
{
"destination": "/etc",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/etc",
"type": "bind"
},
{
"destination": "/lib/modules",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/lib/modules",
"type": "bind"
},
{
"destination": "/root",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/root",
"type": "bind"
},
{
"destination": "/home",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/home",
"type": "bind"
},
{
"destination": "/mnt",
"options": [
"rbind",
"rw",
"rprivate",
"mode=755"
],
"source": "/mnt",
"type": "bind"
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}",
"destination": "/run",
"options": [
"rshared",
"rbind",
"rw",
"mode=755"
]
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}/systemd",
"destination": "/run/systemd",
"options": [
"rslave",
"bind",
"rw",
"mode=755"
]
},
{
"destination": "/var/log",
"options": [
"rbind",
"rslave",
"rw"
],
"source": "/var/log",
"type": "bind"
},
{
"destination": "/var/lib",
"options": [
"rbind",
"rprivate",
"rw"
],
"source": "${STATE_DIRECTORY}",
"type": "bind"
},
{
"destination": "/var/lib/containers/storage",
"options": [
"rbind",
"rshared",
"rw"
],
"source": "${VAR_LIB_CONTAINERS_STORAGE}",
"type": "bind"
},
{
"destination": "/var/lib/origin",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_ORIGIN}",
"type": "bind"
},
{
"destination": "/var/lib/kubelet",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_KUBE}",
"type": "bind"
},
{
"destination": "/opt/cni",
"options": [
"rbind",
"rprivate",
"ro",
"mode=755"
],
"source": "${OPT_CNI}",
"type": "bind"
},
{
"destination": "/dev",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/dev",
"type": "bind"
},
{
"destination": "/sys",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/sys",
"type": "bind"
},
{
"destination": "/proc",
"options": [
"rbind",
"rw",
"mode=755"
],
"source": "/proc",
"type": "proc"
}
]
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0",
"defaultValues": {
"LOG_LEVEL" : "info",
"OPT_CNI" : "/opt/cni",
"VAR_LIB_CONTAINERS_STORAGE" : "/var/lib/containers/storage",
"VAR_LIB_ORIGIN" : "/var/lib/origin",
"VAR_LIB_KUBE" : "/var/lib/kubelet"
}
}

View File

@ -0,0 +1,11 @@
#!/bin/sh
# Ensure that new process maintain this SELinux label
PID=$$
LABEL=`tr -d '\000' < /proc/$PID/attr/current`
printf %s $LABEL > /proc/self/attr/exec
test -e /etc/sysconfig/crio-storage && source /etc/sysconfig/crio-storage
test -e /etc/sysconfig/crio-network && source /etc/sysconfig/crio-network
exec /usr/bin/crio --log-level=$LOG_LEVEL

View File

@ -0,0 +1,20 @@
[Unit]
Description=crio daemon
After=network.target
[Service]
Type=notify
ExecStartPre=/bin/sh $DESTDIR/rootfs/set_mounts.sh
ExecStart=$EXEC_START
ExecStop=$EXEC_STOP
Restart=on-failure
WorkingDirectory=$DESTDIR
RuntimeDirectory=${NAME}
TasksMax=infinity
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
#!/bin/sh
findmnt /var/lib/containers/storage > /dev/null || mount --rbind --make-shared /var/lib/containers/storage /var/lib/containers/storage
findmnt /var/lib/origin > /dev/null || mount --bind --make-shared /var/lib/origin /var/lib/origin
findmnt /var/lib/kubelet > /dev/null || mount --bind --make-shared /var/lib/kubelet /var/lib/kubelet
mount --make-shared /run
findmnt /run/systemd > /dev/null || mount --bind --make-rslave /run/systemd /run/systemd

View File

@ -0,0 +1,5 @@
d ${RUN_DIRECTORY}/crio - - - - -
d /etc/crio - - - - -
Z /etc/crio - - - - -
d ${STATE_DIRECTORY}/origin - - - - -
d ${STATE_DIRECTORY}/kubelet - - - - -

View File

@ -0,0 +1,30 @@
FROM registry.fedoraproject.org/fedora:27
ENV VERSION=0 RELEASE=1 ARCH=x86_64
LABEL com.redhat.component="cri-o" \
name="$FGC/cri-o" \
version="$VERSION" \
release="$RELEASE.$DISTTAG" \
architecture="$ARCH" \
usage="atomic install --system --system-package=no crio && systemctl start crio" \
summary="The cri-o daemon as a system container." \
maintainer="Yu Qi Zhang <jzehrarnyg@gmail.com>" \
atomic.type="system"
COPY README.md /
RUN dnf install --enablerepo=updates-testing --setopt=tsflags=nodocs -y iptables cri-o socat iproute runc && \
rpm -V iptables cri-o iproute runc && \
dnf clean all && \
mkdir -p /exports/hostfs/etc/crio /exports/hostfs/opt/cni/bin/ /exports/hostfs/var/lib/containers/storage/ && \
cp /etc/crio/* /exports/hostfs/etc/crio && \
if test -e /usr/libexec/cni; then cp -Lr /usr/libexec/cni/* /exports/hostfs/opt/cni/bin/; fi
RUN sed -i '/storage_option =/s/.*/&\n"overlay.override_kernel_check=1",/' /exports/hostfs/etc/crio/crio.conf
COPY manifest.json tmpfiles.template config.json.template service.template /exports/
COPY set_mounts.sh /
COPY run.sh /usr/bin/
CMD ["/usr/bin/run.sh"]

View File

@ -0,0 +1,53 @@
# cri-o
This is the cri-o daemon as a system container.
## Building the image from source:
```
# git clone https://github.com/projectatomic/atomic-system-containers
# cd atomic-system-containers/cri-o
# docker build -t crio .
```
## Running the system container, with the atomic CLI:
Pull from registry into ostree:
```
# atomic pull --storage ostree $REGISTRY/crio
```
Or alternatively, pull from local docker:
```
# atomic pull --storage ostree docker:crio:latest
```
Install the container:
Currently we recommend using --system-package=no to avoid having rpmbuild create an rpm file
during installation. This flag will tell the atomic CLI to fall back to copying files to the
host instead.
```
# atomic install --system --system-package=no --name=crio ($REGISTRY)/crio
```
Start as a systemd service:
```
# systemctl start crio
```
Stopping the service
```
# systemctl stop crio
```
Removing the container
```
# atomic uninstall crio
```

View File

@ -0,0 +1,432 @@
{
"ociVersion": "1.0.0",
"platform": {
"arch": "amd64",
"os": "linux"
},
"process": {
"args": [
"/usr/bin/run.sh"
],
"selinuxLabel": "system_u:system_r:container_runtime_t:s0",
"capabilities": {
"ambient": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND",
"CAP_AUDIT_READ"
],
"bounding": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND",
"CAP_AUDIT_READ"
],
"effective": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND",
"CAP_AUDIT_READ"
],
"inheritable": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND",
"CAP_AUDIT_READ"
],
"permitted": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND",
"CAP_AUDIT_READ"
]
},
"cwd": "/",
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin",
"TERM=xterm",
"LOG_LEVEL=$LOG_LEVEL",
"NAME=$NAME"
],
"noNewPrivileges": false,
"terminal": false,
"user": {
"gid": 0,
"uid": 0
}
},
"root": {
"path": "rootfs",
"readonly": true
},
"hooks": {},
"linux": {
"namespaces": [
{
"type": "mount"
}
],
"resources": {
"devices": [
{
"access": "rwm",
"allow": true
}
]
},
"rootfsPropagation": "private"
},
"mounts": [
{
"destination": "/tmp",
"options": [
"private",
"bind",
"rw",
"mode=755"
],
"source": "/tmp",
"type": "bind"
},
{
"destination": "/etc",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/etc",
"type": "bind"
},
{
"destination": "/lib/modules",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/lib/modules",
"type": "bind"
},
{
"destination": "/root",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/root",
"type": "bind"
},
{
"destination": "/home",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/home",
"type": "bind"
},
{
"destination": "/mnt",
"options": [
"rbind",
"rw",
"rprivate",
"mode=755"
],
"source": "/mnt",
"type": "bind"
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}",
"destination": "/run",
"options": [
"rshared",
"rbind",
"rw",
"mode=755"
]
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}/systemd",
"destination": "/run/systemd",
"options": [
"rslave",
"bind",
"rw",
"mode=755"
]
},
{
"destination": "/var/log",
"options": [
"rbind",
"rslave",
"rw"
],
"source": "/var/log",
"type": "bind"
},
{
"destination": "/var/lib",
"options": [
"rbind",
"rprivate",
"rw"
],
"source": "${STATE_DIRECTORY}",
"type": "bind"
},
{
"destination": "/var/lib/containers/storage",
"options": [
"rbind",
"rshared",
"rw"
],
"source": "${VAR_LIB_CONTAINERS_STORAGE}",
"type": "bind"
},
{
"destination": "/var/lib/origin",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_ORIGIN}",
"type": "bind"
},
{
"destination": "/var/lib/kubelet",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_KUBE}",
"type": "bind"
},
{
"destination": "/opt/cni",
"options": [
"rbind",
"rprivate",
"ro",
"mode=755"
],
"source": "${OPT_CNI}",
"type": "bind"
},
{
"destination": "/dev",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/dev",
"type": "bind"
},
{
"destination": "/sys",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/sys",
"type": "bind"
},
{
"destination": "/proc",
"options": [
"rbind",
"rw",
"mode=755"
],
"source": "/proc",
"type": "proc"
}
]
}

View File

@ -0,0 +1,10 @@
{
"version": "1.0",
"defaultValues": {
"LOG_LEVEL" : "info",
"OPT_CNI" : "/opt/cni",
"VAR_LIB_CONTAINERS_STORAGE" : "/var/lib/containers/storage",
"VAR_LIB_ORIGIN" : "/var/lib/origin",
"VAR_LIB_KUBE" : "/var/lib/kubelet"
}
}

View File

@ -0,0 +1,11 @@
#!/bin/sh
# Ensure that new process maintain this SELinux label
PID=$$
LABEL=`tr -d '\000' < /proc/$PID/attr/current`
printf %s $LABEL > /proc/self/attr/exec
test -e /etc/sysconfig/crio-storage && source /etc/sysconfig/crio-storage
test -e /etc/sysconfig/crio-network && source /etc/sysconfig/crio-network
exec /usr/bin/crio --log-level=$LOG_LEVEL

View File

@ -0,0 +1,20 @@
[Unit]
Description=crio daemon
After=network.target
[Service]
Type=notify
ExecStartPre=/bin/sh $DESTDIR/rootfs/set_mounts.sh
ExecStart=$EXEC_START
ExecStop=$EXEC_STOP
Restart=on-failure
WorkingDirectory=$DESTDIR
RuntimeDirectory=${NAME}
TasksMax=infinity
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
#!/bin/sh
findmnt /var/lib/containers/storage > /dev/null || mount --rbind --make-shared /var/lib/containers/storage /var/lib/containers/storage
findmnt /var/lib/origin > /dev/null || mount --bind --make-shared /var/lib/origin /var/lib/origin
findmnt /var/lib/kubelet > /dev/null || mount --bind --make-shared /var/lib/kubelet /var/lib/kubelet
mount --make-shared /run
findmnt /run/systemd > /dev/null || mount --bind --make-rslave /run/systemd /run/systemd

View File

@ -0,0 +1,5 @@
d ${RUN_DIRECTORY}/crio - - - - -
d /etc/crio - - - - -
Z /etc/crio - - - - -
d ${STATE_DIRECTORY}/origin - - - - -
d ${STATE_DIRECTORY}/kubelet - - - - -

View File

@ -0,0 +1,41 @@
#oit## This file is managed by the OpenShift Image Tool
#oit## by the OpenShift Continuous Delivery team.
#oit##
#oit## Any yum repos listed in this file will effectively be ignored during CD builds.
#oit## Yum repos must be enabled in the oit configuration files.
#oit## Some aspects of this file may be managed programmatically. For example, the image name, labels (version,
#oit## release, and other), and the base FROM. Changes made directly in distgit may be lost during the next
#oit## reconciliation.
#oit##
FROM rhel7:7-released
RUN \
yum install --setopt=tsflags=nodocs -y socat iptables cri-o iproute runc skopeo-containers container-selinux && \
rpm -V socat iptables cri-o iproute runc skopeo-containers container-selinux && \
yum clean all && \
mkdir -p /exports/hostfs/etc/crio /exports/hostfs/opt/cni/bin/ /exports/hostfs/var/lib/containers/storage/ && \
cp /etc/crio/* /exports/hostfs/etc/crio && \
if test -e /usr/libexec/cni; then cp -Lr /usr/libexec/cni/* /exports/hostfs/opt/cni/bin/; fi
COPY manifest.json tmpfiles.template config.json.template service.template /exports/
COPY set_mounts.sh /
COPY run.sh /usr/bin/
CMD ["/usr/bin/run.sh"]
LABEL \
com.redhat.component="cri-o-docker" \
io.k8s.description="CRI-O is an implementation of the Kubernetes CRI. It is a lightweight, OCI-compliant runtime that is native to kubernetes. CRI-O supports OCI container images and can pull from any container registry." \
maintainer="Jhon Honce <jhonce@redhat.com>" \
name="openshift3/cri-o" \
License="GPLv2+" \
io.k8s.display-name="CRI-O" \
summary="OCI-based implementation of Kubernetes Container Runtime Interface" \
release="0.13.0.0" \
version="v3.8.0" \
architecture="x86_64" \
usage="atomic install --system --system-package=no crio && systemctl start crio" \
vendor="Red Hat" \
io.openshift.tags="cri-o system rhel7" \
atomic.type="system"

View File

@ -0,0 +1,422 @@
{
"ociVersion": "1.0.0",
"platform": {
"arch": "amd64",
"os": "linux"
},
"process": {
"args": [
"/usr/bin/run.sh"
],
"capabilities": {
"ambient": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"bounding": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"effective": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"inheritable": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
],
"permitted": [
"CAP_CHOWN",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_KILL",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETPCAP",
"CAP_LINUX_IMMUTABLE",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_ADMIN",
"CAP_NET_RAW",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_SYS_MODULE",
"CAP_SYS_RAWIO",
"CAP_SYS_CHROOT",
"CAP_SYS_PTRACE",
"CAP_SYS_PACCT",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_NICE",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_MKNOD",
"CAP_LEASE",
"CAP_AUDIT_WRITE",
"CAP_AUDIT_CONTROL",
"CAP_SETFCAP",
"CAP_DAC_OVERRIDE",
"CAP_MAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_MAC_ADMIN",
"CAP_SYSLOG",
"CAP_WAKE_ALARM",
"CAP_BLOCK_SUSPEND"
]
},
"selinuxLabel": "system_u:system_r:container_runtime_t:s0",
"cwd": "/",
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/root/go/bin",
"TERM=xterm",
"LOG_LEVEL=$LOG_LEVEL",
"NAME=$NAME"
],
"noNewPrivileges": false,
"terminal": false,
"user": {
"gid": 0,
"uid": 0
}
},
"root": {
"path": "rootfs",
"readonly": true
},
"hooks": {},
"linux": {
"namespaces": [{
"type": "mount"
}],
"resources": {
"devices": [{
"access": "rwm",
"allow": true
}]
},
"rootfsPropagation": "private"
},
"mounts": [{
"destination": "/tmp",
"options": [
"private",
"bind",
"rw",
"mode=755"
],
"source": "/tmp",
"type": "bind"
},
{
"destination": "/etc",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/etc",
"type": "bind"
},
{
"destination": "/lib/modules",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/lib/modules",
"type": "bind"
},
{
"destination": "/root",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/root",
"type": "bind"
},
{
"destination": "/home",
"options": [
"rbind",
"rprivate",
"rw",
"mode=755"
],
"source": "/home",
"type": "bind"
},
{
"destination": "/mnt",
"options": [
"rbind",
"rw",
"rprivate",
"mode=755"
],
"source": "/mnt",
"type": "bind"
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}",
"destination": "/run",
"options": [
"rshared",
"rbind",
"rw",
"mode=755"
]
},
{
"type": "bind",
"source": "${RUN_DIRECTORY}/systemd",
"destination": "/run/systemd",
"options": [
"rslave",
"bind",
"rw",
"mode=755"
]
},
{
"destination": "/var/log",
"options": [
"rbind",
"rslave",
"rw"
],
"source": "/var/log",
"type": "bind"
},
{
"destination": "/var/lib",
"options": [
"rbind",
"rprivate",
"rw"
],
"source": "${STATE_DIRECTORY}",
"type": "bind"
},
{
"destination": "/var/lib/containers/storage",
"options": [
"rbind",
"rshared",
"rw"
],
"source": "${VAR_LIB_CONTAINERS_STORAGE}",
"type": "bind"
},
{
"destination": "/var/lib/origin",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_ORIGIN}",
"type": "bind"
},
{
"destination": "/var/lib/kubelet",
"options": [
"rshared",
"bind",
"rw"
],
"source": "${VAR_LIB_KUBE}",
"type": "bind"
},
{
"destination": "/opt/cni",
"options": [
"rbind",
"rprivate",
"ro",
"mode=755"
],
"source": "${OPT_CNI}",
"type": "bind"
},
{
"destination": "/dev",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/dev",
"type": "bind"
},
{
"destination": "/sys",
"options": [
"rprivate",
"rbind",
"rw",
"mode=755"
],
"source": "/sys",
"type": "bind"
},
{
"destination": "/proc",
"options": [
"rbind",
"rw",
"mode=755"
],
"source": "/proc",
"type": "proc"
}
]
}

View File

@ -0,0 +1,37 @@
% CRI-O (1) Container Image Pages
% Jhon Honce
% September 7, 2017
# NAME
cri-o - OCI-based implementation of Kubernetes Container Runtime Interface
# DESCRIPTION
CRI-O is an implementation of the Kubernetes CRI. It is a lightweight, OCI-compliant runtime that is native to kubernetes. CRI-O supports OCI container images and can pull from any container registry.
You can find more information on the CRI-O project at <https://github.com/kubernetes-incubator/cri-o/>
# USAGE
Pull from local docker and install system container:
```
# atomic pull --storage ostree docker:openshift3/cri-o:latest
# atomic install --system --system-package=no --name cri-o openshift3/cri-o
```
Start and enable as a systemd service:
```
# systemctl enable --now cri-o
```
Stopping the service
```
# systemctl stop cri-o
```
Removing the container
```
# atomic uninstall cri-o
```
# SEE ALSO
man systemd(1)

View File

@ -0,0 +1,10 @@
{
"version": "1.0",
"defaultValues": {
"LOG_LEVEL": "info",
"OPT_CNI": "/opt/cni",
"VAR_LIB_CONTAINERS_STORAGE": "/var/lib/containers/storage",
"VAR_LIB_ORIGIN": "/var/lib/origin",
"VAR_LIB_KUBE": "/var/lib/kubelet"
}
}

View File

@ -0,0 +1,11 @@
#!/bin/sh
# Ensure that new process maintain this SELinux label
PID=$$
LABEL=`tr -d '\000' < /proc/$PID/attr/current`
printf %s $LABEL > /proc/self/attr/exec
test -e /etc/sysconfig/crio-storage && source /etc/sysconfig/crio-storage
test -e /etc/sysconfig/crio-network && source /etc/sysconfig/crio-network
exec /usr/bin/crio --log-level=$LOG_LEVEL

View File

@ -0,0 +1,20 @@
[Unit]
Description=crio daemon
After=network.target
[Service]
Type=notify
ExecStartPre=/bin/sh $DESTDIR/rootfs/set_mounts.sh
ExecStart=$EXEC_START
ExecStop=$EXEC_STOP
Restart=on-failure
WorkingDirectory=$DESTDIR
RuntimeDirectory=${NAME}
TasksMax=infinity
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
#!/bin/sh
findmnt /var/lib/containers/storage > /dev/null || mount --rbind --make-shared /var/lib/containers/storage /var/lib/containers/storage
findmnt /var/lib/origin > /dev/null || mount --bind --make-shared /var/lib/origin /var/lib/origin
findmnt /var/lib/kubelet > /dev/null || mount --bind --make-shared /var/lib/kubelet /var/lib/kubelet
mount --make-shared /run
findmnt /run/systemd > /dev/null || mount --bind --make-rslave /run/systemd /run/systemd

View File

@ -0,0 +1,5 @@
d ${RUN_DIRECTORY}/crio - - - - -
d /etc/crio - - - - -
Z /etc/crio - - - - -
d ${STATE_DIRECTORY}/origin - - - - -
d ${STATE_DIRECTORY}/kubelet - - - - -

View File

@ -43,10 +43,12 @@
export CONTAINER_RUNTIME_ENDPOINT='{{ crio_socket }} --runtime-request-timeout=5m'
export ALLOW_SECURITY_CONTEXT=","
export ALLOW_PRIVILEGED=1
export DNS_SERVER_IP={{ ansible_eth0.ipv4.address }}
export API_HOST={{ ansible_eth0.ipv4.address }}
export API_HOST_IP={{ ansible_eth0.ipv4.address }}
export DNS_SERVER_IP={{ ansible_default_ipv4.address }}
export API_HOST={{ ansible_default_ipv4.address }}
export API_HOST_IP={{ ansible_default_ipv4.address }}
export KUBE_ENABLE_CLUSTER_DNS=true
export ENABLE_HOSTPATH_PROVISIONER=true
export KUBE_ENABLE_CLUSTER_DASHBOARD=true
./hack/local-up-cluster.sh
mode: "u=rwx,g=rwx,o=x"

View File

@ -4,7 +4,7 @@
git:
repo: "https://github.com/opencontainers/runc.git"
dest: "{{ ansible_env.GOPATH }}/src/github.com/opencontainers/runc"
version: "84a082bfef6f932de921437815355186db37aeb1"
version: "c6e4a1ebeb1a72b529c6f1b6ee2b1ae5b868b14f"
- name: build runc
make:

View File

@ -16,24 +16,21 @@
- name: Add masquerade for localhost
command: iptables -t nat -I POSTROUTING -s 127.0.0.1 ! -d 127.0.0.1 -j MASQUERADE
# TODO(runcom): enable skipped tests once we fix them (image list related)
# https://github.com/kubernetes-incubator/cri-o/issues/1048
- name: run critest validation
shell: "critest -c --runtime-endpoint /var/run/crio/crio.sock --image-endpoint /var/run/crio/crio.sock -s 'listImage should get exactly 2 repoTags in the result image' v"
shell: "critest -c --runtime-endpoint /var/run/crio/crio.sock --image-endpoint /var/run/crio/crio.sock v"
args:
chdir: "{{ ansible_env.GOPATH }}/src/github.com/kubernetes-incubator/cri-o"
async: 5400
poll: 30
when: ansible_distribution not in ['RedHat', 'CentOS']
# XXX: RHEL has an additional test which fails beacuse of selinux but disabling
# XXX: RHEL has an additional test which fails because of selinux but disabling
# it doesn't solve the issue.
# TODO(runcom): enable skipped tests once we fix them (image list related and selinux)
# https://github.com/kubernetes-incubator/cri-o/issues/1048
# TODO(runcom): enable skipped tests once we fix them (selinux)
# https://bugzilla.redhat.com/show_bug.cgi?id=1414236
# https://access.redhat.com/solutions/2897781
- name: run critest validation
shell: "critest -c --runtime-endpoint /var/run/crio/crio.sock --image-endpoint /var/run/crio/crio.sock -s 'listImage should get exactly 2 repoTags in the result image|should not allow privilege escalation when true' v"
shell: "critest -c --runtime-endpoint /var/run/crio/crio.sock --image-endpoint /var/run/crio/crio.sock -s 'should not allow privilege escalation when true' v"
args:
chdir: "{{ ansible_env.GOPATH }}/src/github.com/kubernetes-incubator/cri-o"
async: 5400

View File

@ -10,7 +10,7 @@
- name: update the server address for the custom cluster
lineinfile:
dest: /usr/local/bin/createcluster.sh
line: "export {{ item }}={{ ansible_eth0.ipv4.address }}"
line: "export {{ item }}={{ ansible_default_ipv4.address }}"
regexp: "^export {{ item }}="
state: present
with_items:
@ -37,13 +37,14 @@
path: "{{ artifacts }}"
state: directory
# TODO remove the last test skipped once https://github.com/kubernetes-incubator/cri-o/pull/1217 is merged
- name: Buffer the e2e testing command to workaround Ansible YAML folding "feature"
set_fact:
e2e_shell_cmd: >
/usr/bin/go run hack/e2e.go
--test
--test_args="-host=https://{{ ansible_default_ipv4.address }}:6443
--ginkgo.focus=\[Conformance\]
--ginkgo.skip=\[Slow\]|\[Serial\]|\[Disruptive\]|\[Flaky\]|\[Feature:.+\]|PersistentVolumes|\[HPA\]|should.support.building.a.client.with.a.CSR|should.support.inline.execution.and.attach
--report-dir={{ artifacts }}"
&> {{ artifacts }}/e2e.log
# Fix vim syntax hilighting: "

View File

@ -41,6 +41,8 @@
tags:
- integration
- e2e
- node-e2e
- critest
tasks:
- name: clone build and install cri-o
include: "build/cri-o.yml"
@ -65,7 +67,7 @@
vars_files:
- "{{ playbook_dir }}/vars.yml"
tags:
- e2e
- critest
tasks:
- name: install Golang tools
include: golang.yml
@ -78,12 +80,46 @@
cri_tools_git_version: "a9e38a4a000bc1a4052fb33de1c967b8cfe9ad40"
- name: run critest validation and benchmarks
include: critest.yml
- hosts: all
remote_user: root
vars_files:
- "{{ playbook_dir }}/vars.yml"
tags:
- node-e2e
tasks:
- name: install Golang tools
include: golang.yml
vars:
version: "1.9.2"
- name: clone build and install kubernetes
include: "build/kubernetes.yml"
vars:
force_clone: True
k8s_git_version: "release-1.9"
k8s_git_version: "master"
k8s_github_fork: "kubernetes"
crio_socket: "/var/run/crio/crio.sock"
- name: run k8s node-e2e tests
include: node-e2e.yml
- hosts: all
remote_user: root
vars_files:
- "{{ playbook_dir }}/vars.yml"
tags:
- e2e
tasks:
- name: install Golang tools
include: golang.yml
vars:
version: "1.9.2"
- name: clone build and install kubernetes
include: "build/kubernetes.yml"
vars:
force_clone: True
# master as of 12/11/2017
k8s_git_version: "master-nfs-fix"
k8s_github_fork: "runcom"
crio_socket: "/var/run/crio/crio.sock"
- name: run k8s e2e tests
include: e2e.yml

View File

@ -0,0 +1,26 @@
---
- name: enable and start CRI-O
systemd:
name: crio
state: started
enabled: yes
daemon_reload: yes
- name: disable SELinux
command: setenforce 0
- name: Flush the iptables
command: iptables -F
- name: run node-e2e tests
shell: |
# parametrize crio socket
# cgroup-driver???
# TODO(runcom): remove conformance focus, we want everything for testgrid
make test-e2e-node PARALLELISM=1 RUNTIME=remote CONTAINER_RUNTIME_ENDPOINT=/var/run/crio.sock IMAGE_SERVICE_ENDPOINT=/var/run/crio/crio.sock TEST_ARGS='--prepull-images=true --kubelet-flags="--cgroup-driver=systemd"' FOCUS="\[Conformance\]" &> {{ artifacts }}/node-e2e.log
args:
chdir: "{{ ansible_env.GOPATH }}/src/k8s.io/kubernetes"
async: 7200
poll: 10
ignore_errors: true

View File

@ -5,6 +5,7 @@
name: "{{ item }}"
state: present
with_items:
- atomic-registries
- container-selinux
- curl
- device-mapper-devel
@ -41,9 +42,9 @@
- ostree-devel
- pkgconfig
- python
- python2-boto
- python2-crypto
- python-devel
- python-rhsm-certificates
- python-virtualenv
- PyYAML
- redhat-rpm-config
@ -57,6 +58,22 @@
async: 600
poll: 10
- name: Add python2-boto for Fedora
package:
name: "{{ item }}"
state: present
with_items:
- python2-boto
when: ansible_distribution in ['Fedora']
- name: Add python-boto for RHEL and CentOS
package:
name: "{{ item }}"
state: present
with_items:
- python-boto
when: ansible_distribution in ['RedHat', 'CentOS']
- name: Add Btrfs for Fedora
package:
name: "{{ item }}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

7
hack/libdm_installed.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
cc -E - > /dev/null 2> /dev/null << EOF
#include <libdevmapper.h>
EOF
if test $? -ne 0 ; then
echo exclude_graphdriver_devicemapper
fi

View File

@ -1,21 +0,0 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
find_files() {
find . -not \( \
\( \
-wholename '*/vendor/*' \
\) -prune \
\) -name '*.go'
}
GOFMT="gofmt -s"
bad_files=$(find_files | xargs $GOFMT -l)
if [[ -n "${bad_files}" ]]; then
echo "!!! '$GOFMT' needs to be run on the following files: "
echo "${bad_files}"
exit 1
fi

View File

@ -53,6 +53,7 @@ type HookParams struct {
Cmds []string `json:"cmds"`
Annotations []string `json:"annotations"`
HasBindMounts bool `json:"hasbindmounts"`
Arguments []string `json:"arguments"`
}
```
@ -63,6 +64,7 @@ type HookParams struct {
| cmds | List of regular expressions to match the command for running the container. If the command matches a regex, the hook will be run | Optional |
| annotations | List of regular expressions to match against the Annotations in the container runtime spec, if an Annotation matches the hook will be run|optional |
| hasbindmounts | Tells CRI-O to run the hook if the container has bind mounts from the host into the container | Optional |
| arguments | Additional arguments to append to the hook command when executing it. For example --debug | Optional |
### Example
@ -85,6 +87,7 @@ cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
"hasbindmounts": true,
"hook": "/usr/libexec/oci/hooks.d/oci-umount",
"stages": [ "prestart" ]
"arguments": [ "--debug" ]
}
```
In this example the oci-umount will only be run during the prestart phase if the container has volume/bind mounts from the host into the container.
In this example the oci-umount will only be run during the prestart phase if the container has volume/bind mounts from the host into the container, it will also execute oci-umount with the --debug argument.

View File

@ -13,17 +13,15 @@ Below, you can find an instruction how to switch one or more nodes on running ku
### Preparing crio
You must prepare and install `crio` on each node you would like to switch. Here's the list of files that must be provided:
You must prepare and install `crio` on each node you would like to switch.
Besides the files installed by `make install install.config`, here's the list of files that must be provided:
| File path | Description | Location |
|--------------------------------------------|----------------------------|-----------------------------------------------------|
| `/etc/crio/crio.conf` | crio configuration | Generated on cri-o `make install` |
| `/etc/crio/seccomp.conf` | seccomp config | Example stored in cri-o repository |
| `/etc/containers/policy.json` | containers policy | Example stored in cri-o repository |
| `/bin/{crio, runc}` | `crio` and `runc` binaries | Built from cri-o repository |
| `/usr/local/libexec/crio/conmon` | `conmon` binary | Built from cri-o repository |
| `/opt/cni/bin/{flannel, bridge,...}` | CNI plugins binaries | Can be built from sources `containernetworking/cni` |
| `/etc/cni/net.d/10-mynet.conf` | Network config | Example stored in [README file](README.md) |
| File path | Description | Location |
|--------------------------------------------|-----------------------------|---------------------------------------------------------|
| `/etc/containers/policy.json` | containers policy | [Example](test/policy.json) stored in cri-o repository |
| `/bin/runc` | `runc` or other OCI runtime | Can be build from sources `opencontainers/runc` |
| `/opt/cni/bin/{flannel, bridge,...}` | CNI plugins binaries | Can be built from sources `containernetworking/plugins` |
| `/etc/cni/net.d/...` | CNI network config | Example [here](contrib/cni) |
`crio` binary can be executed directly on host, inside the container or in any way.
However, recommended way is to set it as a systemd service.

View File

@ -624,8 +624,6 @@ type containerServerState struct {
// AddContainer adds a container to the container state store
func (c *ContainerServer) AddContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sandbox := c.state.sandboxes.Get(ctr.Sandbox())
sandbox.AddContainer(ctr)
c.state.containers.Add(ctr.ID(), ctr)
@ -633,37 +631,26 @@ func (c *ContainerServer) AddContainer(ctr *oci.Container) {
// AddInfraContainer adds a container to the container state store
func (c *ContainerServer) AddInfraContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.infraContainers.Add(ctr.ID(), ctr)
}
// GetContainer returns a container by its ID
func (c *ContainerServer) GetContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.containers.Get(id)
}
// GetInfraContainer returns a container by its ID
func (c *ContainerServer) GetInfraContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.infraContainers.Get(id)
}
// HasContainer checks if a container exists in the state
func (c *ContainerServer) HasContainer(id string) bool {
c.stateLock.Lock()
defer c.stateLock.Unlock()
ctr := c.state.containers.Get(id)
return ctr != nil
return c.state.containers.Get(id) != nil
}
// RemoveContainer removes a container from the container state store
func (c *ContainerServer) RemoveContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sbID := ctr.Sandbox()
sb := c.state.sandboxes.Get(sbID)
sb.RemoveContainer(ctr)
@ -672,15 +659,11 @@ func (c *ContainerServer) RemoveContainer(ctr *oci.Container) {
// RemoveInfraContainer removes a container from the container state store
func (c *ContainerServer) RemoveInfraContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.infraContainers.Delete(ctr.ID())
}
// listContainers returns a list of all containers stored by the server state
func (c *ContainerServer) listContainers() []*oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.containers.List()
}
@ -704,43 +687,36 @@ func (c *ContainerServer) ListContainers(filters ...func(*oci.Container) bool) (
// AddSandbox adds a sandbox to the sandbox state store
func (c *ContainerServer) AddSandbox(sb *sandbox.Sandbox) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.sandboxes.Add(sb.ID(), sb)
c.stateLock.Lock()
c.state.processLevels[selinux.NewContext(sb.ProcessLabel())["level"]]++
c.stateLock.Unlock()
}
// GetSandbox returns a sandbox by its ID
func (c *ContainerServer) GetSandbox(id string) *sandbox.Sandbox {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.sandboxes.Get(id)
}
// GetSandboxContainer returns a sandbox's infra container
func (c *ContainerServer) GetSandboxContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sb := c.state.sandboxes.Get(id)
return sb.InfraContainer()
}
// HasSandbox checks if a sandbox exists in the state
func (c *ContainerServer) HasSandbox(id string) bool {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sb := c.state.sandboxes.Get(id)
return sb != nil
return c.state.sandboxes.Get(id) != nil
}
// RemoveSandbox removes a sandbox from the state store
func (c *ContainerServer) RemoveSandbox(id string) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sb := c.state.sandboxes.Get(id)
processLabel := sb.ProcessLabel()
c.state.sandboxes.Delete(id)
level := selinux.NewContext(processLabel)["level"]
c.stateLock.Lock()
pl, ok := c.state.processLevels[level]
if ok {
c.state.processLevels[level] = pl - 1
@ -749,12 +725,13 @@ func (c *ContainerServer) RemoveSandbox(id string) {
delete(c.state.processLevels, level)
}
}
c.stateLock.Unlock()
c.state.sandboxes.Delete(id)
}
// ListSandboxes lists all sandboxes in the state store
func (c *ContainerServer) ListSandboxes() []*sandbox.Sandbox {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.sandboxes.List()
}

View File

@ -27,6 +27,7 @@ type HookParams struct {
Cmds []string `json:"cmd"`
Annotations []string `json:"annotation"`
HasBindMounts bool `json:"hasbindmounts"`
Arguments []string `json:"arguments"`
}
// readHook reads hooks json files, verifies it and returns the json config

View File

@ -25,8 +25,9 @@ func (c *memoryStore) Add(id string, cont *Sandbox) {
// Get returns a sandbox from the store by id.
func (c *memoryStore) Get(id string) *Sandbox {
var res *Sandbox
c.RLock()
res := c.s[id]
res = c.s[id]
c.RUnlock()
return res
}

View File

@ -25,8 +25,9 @@ func (c *memoryStore) Add(id string, cont *Container) {
// Get returns a container from the store by id.
func (c *memoryStore) Get(id string) *Container {
var res *Container
c.RLock()
res := c.s[id]
res = c.s[id]
c.RUnlock()
return res
}

View File

@ -3,12 +3,13 @@ package storage
import (
"errors"
"net"
"path/filepath"
"path"
"strings"
"github.com/containers/image/copy"
"github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/manifest"
"github.com/containers/image/signature"
istorage "github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
@ -17,20 +18,26 @@ import (
digest "github.com/opencontainers/go-digest"
)
const (
minimumTruncatedIDLength = 3
)
var (
// ErrCannotParseImageID is returned when we try to ResolveNames for an image ID
ErrCannotParseImageID = errors.New("cannot parse an image ID")
// ErrImageMultiplyTagged is returned when we try to remove an image that still has multiple names
ErrImageMultiplyTagged = errors.New("image still has multiple names applied")
)
// ImageResult wraps a subset of information about an image: its ID, its names,
// and the size, if known, or nil if it isn't.
type ImageResult struct {
ID string
Names []string
Size *uint64
// TODO(runcom): this is an hack for https://github.com/kubernetes-incubator/cri-o/pull/1136
// drop this when we have proper image IDs (as in, image IDs should be just
// the config blog digest which is stable across same images).
ID string
Name string
RepoTags []string
RepoDigests []string
Size *uint64
Digest digest.Digest
ConfigDigest digest.Digest
}
@ -47,6 +54,11 @@ type imageService struct {
registries []string
}
// sizer knows its size.
type sizer interface {
Size() (int64, error)
}
// ImageServer wraps up various CRI-related activities into a reusable
// implementation.
type ImageServer interface {
@ -59,6 +71,9 @@ type ImageServer interface {
PrepareImage(systemContext *types.SystemContext, imageName string, options *copy.Options) (types.Image, error)
// PullImage imports an image from the specified location.
PullImage(systemContext *types.SystemContext, imageName string, options *copy.Options) (types.ImageReference, error)
// UntagImage removes a name from the specified image, and if it was
// the only name the image had, removes the image.
UntagImage(systemContext *types.SystemContext, imageName string) error
// RemoveImage deletes the specified image.
RemoveImage(systemContext *types.SystemContext, imageName string) error
// GetStore returns the reference to the storage library Store which
@ -88,6 +103,66 @@ func (svc *imageService) getRef(name string) (types.ImageReference, error) {
return ref, nil
}
func sortNamesByType(names []string) (bestName string, tags, digests []string) {
for _, name := range names {
if len(name) > 72 && name[len(name)-72:len(name)-64] == "@sha256:" {
digests = append(digests, name)
} else {
tags = append(tags, name)
}
}
if len(digests) > 0 {
bestName = digests[0]
}
if len(tags) > 0 {
bestName = tags[0]
}
return bestName, tags, digests
}
func (svc *imageService) makeRepoDigests(knownRepoDigests, tags []string, imageID string) (imageDigest digest.Digest, repoDigests []string) {
// Look up the image's digest.
img, err := svc.store.Image(imageID)
if err != nil {
return "", knownRepoDigests
}
imageDigest = img.Digest
if imageDigest == "" {
imgDigest, err := svc.store.ImageBigDataDigest(imageID, storage.ImageDigestBigDataKey)
if err != nil || imgDigest == "" {
return "", knownRepoDigests
}
imageDigest = imgDigest
}
// If there are no names to convert to canonical references, we're done.
if len(tags) == 0 {
return imageDigest, knownRepoDigests
}
// We only want to supplement what's already explicitly in the list, so keep track of values
// that we already know.
digestMap := make(map[string]struct{})
repoDigests = knownRepoDigests
for _, repoDigest := range knownRepoDigests {
digestMap[repoDigest] = struct{}{}
}
// For each tagged name, parse the name, and if we can extract a named reference, convert
// it into a canonical reference using the digest and add it to the list.
for _, tag := range tags {
if ref, err2 := reference.ParseAnyReference(tag); err2 == nil {
if name, ok := ref.(reference.Named); ok {
trimmed := reference.TrimNamed(name)
if imageRef, err3 := reference.WithDigest(trimmed, imageDigest); err3 == nil {
if _, ok := digestMap[imageRef.String()]; !ok {
repoDigests = append(repoDigests, imageRef.String())
digestMap[imageRef.String()] = struct{}{}
}
}
}
}
}
return imageDigest, repoDigests
}
func (svc *imageService) ListImages(systemContext *types.SystemContext, filter string) ([]ImageResult, error) {
results := []ImageResult{}
if filter != "" {
@ -96,16 +171,26 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
return nil, err
}
if image, err := istorage.Transport.GetStoreImage(svc.store, ref); err == nil {
img, err := ref.NewImage(systemContext)
img, err := ref.NewImageSource(systemContext)
if err != nil {
return nil, err
}
size := imageSize(img)
configDigest, err := imageConfigDigest(img, nil)
img.Close()
if err != nil {
return nil, err
}
name, tags, digests := sortNamesByType(image.Names)
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
results = append(results, ImageResult{
ID: image.ID,
Names: image.Names,
Size: size,
ID: image.ID,
Name: name,
RepoTags: tags,
RepoDigests: repoDigests,
Size: size,
Digest: imageDigest,
ConfigDigest: configDigest,
})
}
} else {
@ -118,16 +203,26 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
if err != nil {
return nil, err
}
img, err := ref.NewImage(systemContext)
img, err := ref.NewImageSource(systemContext)
if err != nil {
return nil, err
}
size := imageSize(img)
configDigest, err := imageConfigDigest(img, nil)
img.Close()
if err != nil {
return nil, err
}
name, tags, digests := sortNamesByType(image.Names)
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
results = append(results, ImageResult{
ID: image.ID,
Names: image.Names,
Size: size,
ID: image.ID,
Name: name,
RepoTags: tags,
RepoDigests: repoDigests,
Size: size,
Digest: imageDigest,
ConfigDigest: configDigest,
})
}
}
@ -152,29 +247,54 @@ func (svc *imageService) ImageStatus(systemContext *types.SystemContext, nameOrI
return nil, err
}
img, err := ref.NewImage(systemContext)
img, err := ref.NewImageSource(systemContext)
if err != nil {
return nil, err
}
defer img.Close()
size := imageSize(img)
configDigest, err := imageConfigDigest(img, nil)
if err != nil {
return nil, err
}
return &ImageResult{
name, tags, digests := sortNamesByType(image.Names)
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
result := ImageResult{
ID: image.ID,
Names: image.Names,
Name: name,
RepoTags: tags,
RepoDigests: repoDigests,
Size: size,
ConfigDigest: img.ConfigInfo().Digest,
}, nil
Digest: imageDigest,
ConfigDigest: configDigest,
}
return &result, nil
}
func imageSize(img types.Image) *uint64 {
if sum, err := img.Size(); err == nil {
usum := uint64(sum)
return &usum
func imageSize(img types.ImageSource) *uint64 {
if s, ok := img.(sizer); ok {
if sum, err := s.Size(); err == nil {
usum := uint64(sum)
return &usum
}
}
return nil
}
func imageConfigDigest(img types.ImageSource, instanceDigest *digest.Digest) (digest.Digest, error) {
manifestBytes, manifestType, err := img.GetManifest(instanceDigest)
if err != nil {
return "", err
}
imgManifest, err := manifest.FromBlob(manifestBytes, manifestType)
if err != nil {
return "", err
}
return imgManifest.ConfigInfo().Digest, nil
}
func (svc *imageService) CanPull(imageName string, options *copy.Options) (bool, error) {
srcRef, err := svc.prepareReference(imageName, options)
if err != nil {
@ -184,7 +304,11 @@ func (svc *imageService) CanPull(imageName string, options *copy.Options) (bool,
if err != nil {
return false, err
}
src, err := image.FromSource(rawSource)
sourceCtx := &types.SystemContext{}
if options.SourceCtx != nil {
sourceCtx = options.SourceCtx
}
src, err := image.FromSource(sourceCtx, rawSource)
if err != nil {
rawSource.Close()
return false, err
@ -274,6 +398,57 @@ func (svc *imageService) PullImage(systemContext *types.SystemContext, imageName
return destRef, nil
}
func (svc *imageService) UntagImage(systemContext *types.SystemContext, nameOrID string) error {
ref, err := alltransports.ParseImageName(nameOrID)
if err != nil {
ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID)
if err2 != nil {
ref3, err3 := istorage.Transport.ParseStoreReference(svc.store, nameOrID)
if err3 != nil {
return err
}
ref2 = ref3
}
ref = ref2
}
img, err := istorage.Transport.GetStoreImage(svc.store, ref)
if err != nil {
return err
}
if !strings.HasPrefix(img.ID, nameOrID) {
namedRef, err := svc.prepareReference(nameOrID, &copy.Options{})
if err != nil {
return err
}
name := nameOrID
if namedRef.DockerReference() != nil {
name = namedRef.DockerReference().Name()
if tagged, ok := namedRef.DockerReference().(reference.NamedTagged); ok {
name = name + ":" + tagged.Tag()
}
if canonical, ok := namedRef.DockerReference().(reference.Canonical); ok {
name = name + "@" + canonical.Digest().String()
}
}
prunedNames := make([]string, 0, len(img.Names))
for _, imgName := range img.Names {
if imgName != name && imgName != nameOrID {
prunedNames = append(prunedNames, imgName)
}
}
if len(prunedNames) > 0 {
return svc.store.SetNames(img.ID, prunedNames)
}
}
return ref.DeleteImage(systemContext)
}
func (svc *imageService) RemoveImage(systemContext *types.SystemContext, nameOrID string) error {
ref, err := alltransports.ParseImageName(nameOrID)
if err != nil {
@ -341,6 +516,14 @@ func splitDockerDomain(name string) (domain, remainder string) {
}
func (svc *imageService) ResolveNames(imageName string) ([]string, error) {
// _Maybe_ it's a truncated image ID. Don't prepend a registry name, then.
if len(imageName) >= minimumTruncatedIDLength && svc.store != nil {
if img, err := svc.store.Image(imageName); err == nil && img != nil && strings.HasPrefix(img.ID, imageName) {
// It's a truncated version of the ID of an image that's present in local storage;
// we need to expand it.
return []string{img.ID}, nil
}
}
// This to prevent any image ID to go through this routine
_, err := reference.ParseNormalizedNamed(imageName)
if err != nil {
@ -368,7 +551,7 @@ func (svc *imageService) ResolveNames(imageName string) ([]string, error) {
if r == "docker.io" && !strings.ContainsRune(remainder, '/') {
rem = "library/" + rem
}
images = append(images, filepath.Join(r, rem))
images = append(images, path.Join(r, rem))
}
return images, nil
}

View File

@ -14,7 +14,6 @@ import (
"strings"
"time"
"github.com/docker/distribution/reference"
dockermounts "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/symlink"
@ -427,15 +426,18 @@ func buildOCIProcessArgs(containerKubeConfig *pb.ContainerConfig, imageOCIConfig
func addOCIHook(specgen *generate.Generator, hook lib.HookParams) error {
logrus.Debugf("AddOCIHook", hook)
for _, stage := range hook.Stage {
h := rspec.Hook{
Path: hook.Hook,
Args: append([]string{hook.Hook}, hook.Arguments...),
Env: []string{fmt.Sprintf("stage=%s", stage)},
}
switch stage {
case "prestart":
specgen.AddPreStartHook(hook.Hook, []string{hook.Hook, "prestart"})
specgen.AddPreStartHook(h)
case "poststart":
specgen.AddPostStartHook(hook.Hook, []string{hook.Hook, "poststart"})
specgen.AddPostStartHook(h)
case "poststop":
specgen.AddPostStopHook(hook.Hook, []string{hook.Hook, "poststop"})
specgen.AddPostStopHook(h)
}
}
return nil
@ -488,6 +490,110 @@ func setupContainerUser(specgen *generate.Generator, rootfs string, sc *pb.Linux
return nil
}
// setupCapabilities sets process.capabilities in the OCI runtime config.
func setupCapabilities(specgen *generate.Generator, capabilities *pb.Capability) error {
if capabilities == nil {
return nil
}
toCAPPrefixed := func(cap string) string {
if !strings.HasPrefix(strings.ToLower(cap), "cap_") {
return "CAP_" + strings.ToUpper(cap)
}
return cap
}
// Add/drop all capabilities if "all" is specified, so that
// following individual add/drop could still work. E.g.
// AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"}
// will be all capabilities without `CAP_CHOWN`.
// see https://github.com/kubernetes/kubernetes/issues/51980
if inStringSlice(capabilities.GetAddCapabilities(), "ALL") {
for _, c := range getOCICapabilitiesList() {
if err := specgen.AddProcessCapabilityAmbient(c); err != nil {
return err
}
if err := specgen.AddProcessCapabilityBounding(c); err != nil {
return err
}
if err := specgen.AddProcessCapabilityEffective(c); err != nil {
return err
}
if err := specgen.AddProcessCapabilityInheritable(c); err != nil {
return err
}
if err := specgen.AddProcessCapabilityPermitted(c); err != nil {
return err
}
}
}
if inStringSlice(capabilities.GetDropCapabilities(), "ALL") {
for _, c := range getOCICapabilitiesList() {
if err := specgen.DropProcessCapabilityAmbient(c); err != nil {
return err
}
if err := specgen.DropProcessCapabilityBounding(c); err != nil {
return err
}
if err := specgen.DropProcessCapabilityEffective(c); err != nil {
return err
}
if err := specgen.DropProcessCapabilityInheritable(c); err != nil {
return err
}
if err := specgen.DropProcessCapabilityPermitted(c); err != nil {
return err
}
}
}
for _, cap := range capabilities.GetAddCapabilities() {
if strings.ToUpper(cap) == "ALL" {
continue
}
capPrefixed := toCAPPrefixed(cap)
if err := specgen.AddProcessCapabilityAmbient(capPrefixed); err != nil {
return err
}
if err := specgen.AddProcessCapabilityBounding(capPrefixed); err != nil {
return err
}
if err := specgen.AddProcessCapabilityEffective(capPrefixed); err != nil {
return err
}
if err := specgen.AddProcessCapabilityInheritable(capPrefixed); err != nil {
return err
}
if err := specgen.AddProcessCapabilityPermitted(capPrefixed); err != nil {
return err
}
}
for _, cap := range capabilities.GetDropCapabilities() {
if strings.ToUpper(cap) == "ALL" {
continue
}
capPrefixed := toCAPPrefixed(cap)
if err := specgen.DropProcessCapabilityAmbient(capPrefixed); err != nil {
return fmt.Errorf("failed to drop cap %s %v", capPrefixed, err)
}
if err := specgen.DropProcessCapabilityBounding(capPrefixed); err != nil {
return fmt.Errorf("failed to drop cap %s %v", capPrefixed, err)
}
if err := specgen.DropProcessCapabilityEffective(capPrefixed); err != nil {
return fmt.Errorf("failed to drop cap %s %v", capPrefixed, err)
}
if err := specgen.DropProcessCapabilityInheritable(capPrefixed); err != nil {
return fmt.Errorf("failed to drop cap %s %v", capPrefixed, err)
}
if err := specgen.DropProcessCapabilityPermitted(capPrefixed); err != nil {
return fmt.Errorf("failed to drop cap %s %v", capPrefixed, err)
}
}
return nil
}
func hostNetwork(containerConfig *pb.ContainerConfig) bool {
securityContext := containerConfig.GetLinux().GetSecurityContext()
if securityContext == nil || securityContext.GetNamespaceOptions() == nil {
@ -563,7 +669,11 @@ func (s *Server) CreateContainer(ctx context.Context, req *pb.CreateContainerReq
return nil, fmt.Errorf("CreateContainerRequest.ContainerConfig is nil")
}
name := containerConfig.GetMetadata().Name
if containerConfig.GetMetadata() == nil {
return nil, fmt.Errorf("CreateContainerRequest.ContainerConfig.Metadata is nil")
}
name := containerConfig.GetMetadata().GetName()
if name == "" {
return nil, fmt.Errorf("CreateContainerRequest.ContainerConfig.Name is empty")
}
@ -711,8 +821,14 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
}
specgen.AddAnnotation(annotations.Volumes, string(volumesJSON))
mnt := rspec.Mount{
Destination: "/sys/fs/cgroup",
Type: "cgroup",
Source: "cgroup",
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
}
// Add cgroup mount so container process can introspect its own limits
specgen.AddCgroupsMount("ro")
specgen.AddMount(mnt)
if err := addDevices(sb, containerConfig, &specgen); err != nil {
return nil, err
@ -786,28 +902,13 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
if linux != nil {
resources := linux.GetResources()
if resources != nil {
cpuPeriod := resources.CpuPeriod
if cpuPeriod != 0 {
specgen.SetLinuxResourcesCPUPeriod(uint64(cpuPeriod))
}
cpuQuota := resources.CpuQuota
if cpuQuota != 0 {
specgen.SetLinuxResourcesCPUQuota(cpuQuota)
}
cpuShares := resources.CpuShares
if cpuShares != 0 {
specgen.SetLinuxResourcesCPUShares(uint64(cpuShares))
}
memoryLimit := resources.MemoryLimitInBytes
if memoryLimit != 0 {
specgen.SetLinuxResourcesMemoryLimit(memoryLimit)
}
oomScoreAdj := resources.OomScoreAdj
specgen.SetProcessOOMScoreAdj(int(oomScoreAdj))
specgen.SetLinuxResourcesCPUPeriod(uint64(resources.GetCpuPeriod()))
specgen.SetLinuxResourcesCPUQuota(resources.GetCpuQuota())
specgen.SetLinuxResourcesCPUShares(uint64(resources.GetCpuShares()))
specgen.SetLinuxResourcesMemoryLimit(resources.GetMemoryLimitInBytes())
specgen.SetProcessOOMScoreAdj(int(resources.GetOomScoreAdj()))
specgen.SetLinuxResourcesCPUCpus(resources.GetCpusetCpus())
specgen.SetLinuxResourcesCPUMems(resources.GetCpusetMems())
}
var cgPath string
@ -826,57 +927,13 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
}
specgen.SetLinuxCgroupsPath(cgPath)
capabilities := linux.GetSecurityContext().GetCapabilities()
if privileged {
// this is setting correct capabilities as well for privileged mode
specgen.SetupPrivileged(true)
setOCIBindMountsPrivileged(&specgen)
} else {
toCAPPrefixed := func(cap string) string {
if !strings.HasPrefix(strings.ToLower(cap), "cap_") {
return "CAP_" + strings.ToUpper(cap)
}
return cap
}
// Add/drop all capabilities if "all" is specified, so that
// following individual add/drop could still work. E.g.
// AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"}
// will be all capabilities without `CAP_CHOWN`.
// see https://github.com/kubernetes/kubernetes/issues/51980
if inStringSlice(capabilities.GetAddCapabilities(), "ALL") {
for _, c := range getOCICapabilitiesList() {
if err := specgen.AddProcessCapability(c); err != nil {
return nil, err
}
}
}
if inStringSlice(capabilities.GetDropCapabilities(), "ALL") {
for _, c := range getOCICapabilitiesList() {
if err := specgen.DropProcessCapability(c); err != nil {
return nil, err
}
}
}
if capabilities != nil {
for _, cap := range capabilities.GetAddCapabilities() {
if strings.ToUpper(cap) == "ALL" {
continue
}
if err := specgen.AddProcessCapability(toCAPPrefixed(cap)); err != nil {
return nil, err
}
}
for _, cap := range capabilities.GetDropCapabilities() {
if strings.ToUpper(cap) == "ALL" {
continue
}
if err := specgen.DropProcessCapability(toCAPPrefixed(cap)); err != nil {
return nil, fmt.Errorf("failed to drop cap %s %v", toCAPPrefixed(cap), err)
}
}
err = setupCapabilities(&specgen, linux.GetSecurityContext().GetCapabilities())
if err != nil {
return nil, err
}
}
specgen.SetProcessSelinuxLabel(processLabel)
@ -963,46 +1020,31 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
return nil, err
}
}
image = images[0]
// Get imageName and imageRef that are requested in container status
imageName := image
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), image)
// Get imageName and imageRef that are later requested in container status
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), images[0])
if err != nil {
return nil, err
}
imageName := status.Name
imageRef := status.ID
//
// TODO: https://github.com/kubernetes-incubator/cri-o/issues/531
//
//for _, n := range status.Names {
//r, err := reference.ParseNormalizedNamed(n)
//if err != nil {
//return nil, fmt.Errorf("failed to normalize image name for ImageRef: %v", err)
//}
//if digested, isDigested := r.(reference.Canonical); isDigested {
//imageRef = reference.FamiliarString(digested)
//break
//}
//}
for _, n := range status.Names {
r, err := reference.ParseNormalizedNamed(n)
if err != nil {
return nil, fmt.Errorf("failed to normalize image name for Image: %v", err)
}
if tagged, isTagged := r.(reference.Tagged); isTagged {
imageName = reference.FamiliarString(tagged)
break
}
if len(status.RepoDigests) > 0 {
imageRef = status.RepoDigests[0]
}
specgen.AddAnnotation(annotations.Image, image)
specgen.AddAnnotation(annotations.ImageName, imageName)
specgen.AddAnnotation(annotations.ImageRef, imageRef)
specgen.AddAnnotation(annotations.IP, sb.IP())
mnt = rspec.Mount{
Type: "bind",
Source: sb.ShmPath(),
Destination: "/etc/shm",
Options: []string{"rw", "bind"},
}
// bind mount the pod shm
specgen.AddBindMount(sb.ShmPath(), "/dev/shm", []string{"rw"})
specgen.AddMount(mnt)
options := []string{"rw"}
if readOnlyRootfs {
@ -1013,8 +1055,14 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
return nil, err
}
mnt = rspec.Mount{
Type: "bind",
Source: sb.ResolvPath(),
Destination: "/etc/resolv.conf",
Options: append(options, "bind"),
}
// bind mount the pod resolver file
specgen.AddBindMount(sb.ResolvPath(), "/etc/resolv.conf", options)
specgen.AddMount(mnt)
}
if sb.HostnamePath() != "" {
@ -1022,12 +1070,24 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
return nil, err
}
specgen.AddBindMount(sb.HostnamePath(), "/etc/hostname", options)
mnt = rspec.Mount{
Type: "bind",
Source: sb.HostnamePath(),
Destination: "/etc/hostname",
Options: append(options, "bind"),
}
specgen.AddMount(mnt)
}
// Bind mount /etc/hosts for host networking containers
if hostNetwork(containerConfig) {
specgen.AddBindMount("/etc/hosts", "/etc/hosts", options)
mnt = rspec.Mount{
Type: "bind",
Source: "/etc/hosts",
Destination: "/etc/hosts",
Options: append(options, "bind"),
}
specgen.AddMount(mnt)
}
// Set hostname and add env for hostname
@ -1043,7 +1103,6 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
specgen.AddAnnotation(annotations.TTY, fmt.Sprintf("%v", containerConfig.Tty))
specgen.AddAnnotation(annotations.Stdin, fmt.Sprintf("%v", containerConfig.Stdin))
specgen.AddAnnotation(annotations.StdinOnce, fmt.Sprintf("%v", containerConfig.StdinOnce))
specgen.AddAnnotation(annotations.Image, image)
specgen.AddAnnotation(annotations.ResolvPath, sb.InfraContainer().CrioAnnotations()[annotations.ResolvPath])
created := time.Now()
@ -1079,7 +1138,7 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
attempt := metadata.Attempt
containerInfo, err := s.StorageRuntimeServer().CreateContainer(s.ImageContext(),
sb.Name(), sb.ID(),
image, image,
image, status.ID,
containerName, containerID,
metaname,
attempt,
@ -1088,6 +1147,14 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
if err != nil {
return nil, err
}
defer func() {
if err != nil {
err2 := s.StorageRuntimeServer().DeleteContainer(containerInfo.ID)
if err2 != nil {
logrus.Warnf("Failed to cleanup container directory: %v", err2)
}
}
}()
mountPoint, err := s.StorageRuntimeServer().StartContainer(containerID)
if err != nil {
@ -1097,7 +1164,8 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
containerImageConfig := containerInfo.Config
if containerImageConfig == nil {
return nil, fmt.Errorf("empty image config for %s", image)
err = fmt.Errorf("empty image config for %s", image)
return nil, err
}
if containerImageConfig.Config.StopSignal != "" {
@ -1161,7 +1229,13 @@ func (s *Server) createSandboxContainer(ctx context.Context, containerID string,
sort.Sort(orderedMounts(mounts))
for _, m := range mounts {
specgen.AddBindMount(m.Source, m.Destination, m.Options)
mnt = rspec.Mount{
Type: "bind",
Source: m.Source,
Destination: m.Destination,
Options: append(m.Options, "bind"),
}
specgen.AddMount(mnt)
}
if err := s.setupOCIHooks(&specgen, sb, containerConfig, processArgs[0]); err != nil {

View File

@ -97,6 +97,7 @@ func (s *Server) ListContainers(ctx context.Context, req *pb.ListContainersReque
Metadata: ctr.Metadata(),
Annotations: ctr.Annotations(),
Image: img,
ImageRef: ctr.ImageRef(),
}
switch cState.Status {

View File

@ -3,6 +3,7 @@ package server
import (
"time"
"github.com/containers/image/types"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@ -38,7 +39,10 @@ func (s *Server) ContainerStatus(ctx context.Context, req *pb.ContainerStatusReq
ImageRef: c.ImageRef(),
},
}
resp.Status.Image = &pb.ImageSpec{Image: c.ImageName()}
resp.Status.Image = &pb.ImageSpec{Image: c.Image()}
if status, err := s.StorageImageServer().ImageStatus(&types.SystemContext{}, c.ImageRef()); err == nil {
resp.Status.Image.Image = status.Name
}
mounts := []*pb.Mount{}
for _, cv := range c.Volumes() {

View File

@ -33,14 +33,16 @@ func (s *Server) ListImages(ctx context.Context, req *pb.ListImagesRequest) (res
for _, result := range results {
if result.Size != nil {
resp.Images = append(resp.Images, &pb.Image{
Id: result.ID,
RepoTags: result.Names,
Size_: *result.Size,
Id: result.ID,
RepoTags: result.RepoTags,
RepoDigests: result.RepoDigests,
Size_: *result.Size,
})
} else {
resp.Images = append(resp.Images, &pb.Image{
Id: result.ID,
RepoTags: result.Names,
Id: result.ID,
RepoTags: result.RepoTags,
RepoDigests: result.RepoDigests,
})
}
}

View File

@ -104,8 +104,16 @@ func (s *Server) PullImage(ctx context.Context, req *pb.PullImageRequest) (resp
if pulled == "" && err != nil {
return nil, err
}
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), pulled)
if err != nil {
return nil, err
}
imageRef := status.ID
if len(status.RepoDigests) > 0 {
imageRef = status.RepoDigests[0]
}
resp = &pb.PullImageResponse{
ImageRef: pulled,
ImageRef: imageRef,
}
logrus.Debugf("PullImageResponse: %+v", resp)
return resp, nil

View File

@ -40,7 +40,7 @@ func (s *Server) RemoveImage(ctx context.Context, req *pb.RemoveImageRequest) (r
}
}
for _, img := range images {
err = s.StorageImageServer().RemoveImage(s.ImageContext(), img)
err = s.StorageImageServer().UntagImage(s.ImageContext(), img)
if err != nil {
logrus.Debugf("error deleting image %s: %v", img, err)
continue

View File

@ -48,10 +48,10 @@ func (s *Server) ImageStatus(ctx context.Context, req *pb.ImageStatusRequest) (r
}
resp = &pb.ImageStatusResponse{
Image: &pb.Image{
Id: status.ID,
RepoTags: status.Names,
Size_: *status.Size,
// TODO: https://github.com/kubernetes-incubator/cri-o/issues/531
Id: status.ID,
RepoTags: status.RepoTags,
RepoDigests: status.RepoDigests,
Size_: *status.Size,
},
}
logrus.Debugf("ImageStatusResponse: %+v", resp)

View File

@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
cimage "github.com/containers/image/types"
"github.com/go-zoo/bone"
"github.com/kubernetes-incubator/cri-o/lib/sandbox"
"github.com/kubernetes-incubator/cri-o/oci"
@ -45,10 +46,17 @@ func (s *Server) getContainerInfo(id string, getContainerFunc func(id string) *o
logrus.Debugf("can't find sandbox %s for container %s", ctr.Sandbox(), id)
return types.ContainerInfo{}, errSandboxNotFound
}
image := ctr.Image()
if s.ContainerServer != nil && s.ContainerServer.StorageImageServer() != nil {
if status, err := s.ContainerServer.StorageImageServer().ImageStatus(&cimage.SystemContext{}, ctr.ImageRef()); err == nil {
image = status.Name
}
}
return types.ContainerInfo{
Name: ctr.Name(),
Pid: ctrState.Pid,
Image: ctr.ImageName(),
Image: image,
ImageRef: ctr.ImageRef(),
CreatedTime: ctrState.Created.UnixNano(),
Labels: ctr.Labels(),
Annotations: ctr.Annotations(),

View File

@ -67,7 +67,7 @@ func TestGetContainerInfo(t *testing.T) {
"io.kubernetes.test1": "value1",
}
getContainerFunc := func(id string) *oci.Container {
container, err := oci.NewContainer("testid", "testname", "", "/container/logs", mockNetNS{}, labels, annotations, annotations, "imageName", "imageName", "imageRef", &runtime.ContainerMetadata{}, "testsandboxid", false, false, false, false, false, "/root/for/container", created, "SIGKILL")
container, err := oci.NewContainer("testid", "testname", "", "/container/logs", mockNetNS{}, labels, annotations, annotations, "image", "imageName", "imageRef", &runtime.ContainerMetadata{}, "testsandboxid", false, false, false, false, false, "/root/for/container", created, "SIGKILL")
if err != nil {
t.Fatal(err)
}
@ -101,8 +101,11 @@ func TestGetContainerInfo(t *testing.T) {
if ci.Name != "testname" {
t.Fatalf("expected name testname, got %s", ci.Name)
}
if ci.Image != "imageName" {
t.Fatalf("expected image name imageName, got %s", ci.Image)
if ci.Image != "image" {
t.Fatalf("expected image name image, got %s", ci.Image)
}
if ci.ImageRef != "imageRef" {
t.Fatalf("expected image ref imageRef, got %s", ci.ImageRef)
}
if ci.Root != "/var/foo/container" {
t.Fatalf("expected root to be /var/foo/container, got %s", ci.Root)

View File

@ -101,16 +101,20 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest
s.updateLock.RLock()
defer s.updateLock.RUnlock()
if req.GetConfig().GetMetadata() == nil {
return nil, fmt.Errorf("CreateContainerRequest.ContainerConfig.Metadata is nil")
}
logrus.Debugf("RunPodSandboxRequest %+v", req)
var processLabel, mountLabel, resolvPath string
// process req.Name
kubeName := req.GetConfig().GetMetadata().Name
kubeName := req.GetConfig().GetMetadata().GetName()
if kubeName == "" {
return nil, fmt.Errorf("PodSandboxConfig.Name should not be empty")
}
namespace := req.GetConfig().GetMetadata().Namespace
attempt := req.GetConfig().GetMetadata().Attempt
namespace := req.GetConfig().GetMetadata().GetNamespace()
attempt := req.GetConfig().GetMetadata().GetAttempt()
id, name, err := s.generatePodIDandName(req.GetConfig())
if err != nil {
@ -156,8 +160,8 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest
name, id,
s.config.PauseImage, "",
containerName,
req.GetConfig().GetMetadata().Name,
req.GetConfig().GetMetadata().Uid,
req.GetConfig().GetMetadata().GetName(),
req.GetConfig().GetMetadata().GetUid(),
namespace,
attempt,
nil)
@ -210,8 +214,13 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest
if err := label.Relabel(resolvPath, mountLabel, true); err != nil && err != unix.ENOTSUP {
return nil, err
}
g.AddBindMount(resolvPath, "/etc/resolv.conf", []string{"ro"})
mnt := runtimespec.Mount{
Type: "bind",
Source: resolvPath,
Destination: "/etc/resolv.conf",
Options: []string{"ro", "bind"},
}
g.AddMount(mnt)
}
// add metadata
@ -480,7 +489,13 @@ func (s *Server) RunPodSandbox(ctx context.Context, req *pb.RunPodSandboxRequest
if err := label.Relabel(hostnamePath, mountLabel, true); err != nil && err != unix.ENOTSUP {
return nil, err
}
g.AddBindMount(hostnamePath, "/etc/hostname", []string{"ro"})
mnt := runtimespec.Mount{
Type: "bind",
Source: hostnamePath,
Destination: "/etc/hostname",
Options: []string{"ro", "bind"},
}
g.AddMount(mnt)
g.AddAnnotation(annotations.HostnamePath, hostnamePath)
sb.AddHostnamePath(hostnamePath)

View File

@ -41,11 +41,12 @@ You will also need to install the [CNI](https://github.com/containernetworking/c
the the default pod test template runs without host networking:
```
$ go get github.com/containernetworking/cni
$ cd "$GOPATH/src/github.com/containernetworking/cni"
$ git checkout -q d4bbce1865270cd2d2be558d6a23e63d314fe769
$ ./build.sh \
$ mkdir -p /opt/cni/bin \
$ cd "$GOPATH/src/github.com/containernetworking"
$ git clone https://github.com/containernetworking/plugins.git
$ cd plugins
$ git checkout -q dcf7368eeab15e2affc6256f0bb1e84dd46a34de
$ ./build.sh
$ mkdir -p /opt/cni/bin
$ cp bin/* /opt/cni/bin/
```
@ -69,11 +70,11 @@ Tests on the host will run with `runc` as the default runtime.
However you can select other OCI compatible runtimes by setting
the `RUNTIME` environment variable.
For example one could use the [Clear Containers](https://github.com/01org/cc-oci-runtime/wiki/Installation)
For example one could use the [Clear Containers](https://github.com/clearcontainers/runtime)
runtime instead of `runc`:
```
make localintegration RUNTIME=cc-oci-runtime
make localintegration RUNTIME=cc-runtime
```
## Writing integration tests

View File

@ -1061,3 +1061,31 @@ function teardown() {
cleanup_pods
stop_crio
}
@test "ctr resources" {
start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
pod_id="$output"
run crictl create "$pod_id" "$TESTDATA"/container_redis.json "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
ctr_id="$output"
run crictl start "$ctr_id"
echo "$output"
[ "$status" -eq 0 ]
run crictl exec --sync "$ctr_id" sh -c "cat /sys/fs/cgroup/cpuset/cpuset.cpus"
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "0-1" ]]
run crictl exec --sync "$ctr_id" sh -c "cat /sys/fs/cgroup/cpuset/cpuset.mems"
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "0" ]]
cleanup_ctrs
cleanup_pods
stop_crio
}

View File

@ -103,7 +103,7 @@ cp "$CONMON_BINARY" "$TESTDIR/conmon"
PATH=$PATH:$TESTDIR
# Make sure we have a copy of the redis:latest image.
# Make sure we have a copy of the redis:alpine image.
if ! [ -d "$ARTIFACTS_PATH"/redis-image ]; then
mkdir -p "$ARTIFACTS_PATH"/redis-image
if ! "$COPYIMG_BINARY" --import-from=docker://redis:alpine --export-to=dir:"$ARTIFACTS_PATH"/redis-image --signature-policy="$INTEGRATION_ROOT"/policy.json ; then
@ -113,19 +113,6 @@ if ! [ -d "$ARTIFACTS_PATH"/redis-image ]; then
fi
fi
# TODO: remove the code below for redis digested image id when
# https://github.com/kubernetes-incubator/cri-o/issues/531 is complete
# as the digested reference will be auto-stored when pulling the tag
# above
if ! [ -d "$ARTIFACTS_PATH"/redis-image-digest ]; then
mkdir -p "$ARTIFACTS_PATH"/redis-image-digest
if ! "$COPYIMG_BINARY" --import-from=docker://redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b --export-to=dir:"$ARTIFACTS_PATH"/redis-image-digest --signature-policy="$INTEGRATION_ROOT"/policy.json ; then
echo "Error pulling docker://redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b"
rm -fr "$ARTIFACTS_PATH"/redis-image-digest
exit 1
fi
fi
# Make sure we have a copy of the runcom/stderr-test image.
if ! [ -d "$ARTIFACTS_PATH"/stderr-test ]; then
mkdir -p "$ARTIFACTS_PATH"/stderr-test
@ -225,16 +212,11 @@ function start_crio() {
if ! [ "$3" = "--no-pause-image" ] ; then
"$BIN2IMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --source-binary "$PAUSE_BINARY"
fi
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=redis:alpine --import-from=dir:"$ARTIFACTS_PATH"/redis-image --add-name=docker.io/library/redis:alpine --signature-policy="$INTEGRATION_ROOT"/policy.json
# TODO: remove the code below for redis:alpine digested image id when
# https://github.com/kubernetes-incubator/cri-o/issues/531 is complete
# as the digested reference will be auto-stored when pulling the tag
# above
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b --import-from=dir:"$ARTIFACTS_PATH"/redis-image-digest --add-name=docker.io/library/redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=mrunalp/oom --import-from=dir:"$ARTIFACTS_PATH"/oom-image --add-name=docker.io/library/mrunalp/oom --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=mrunalp/image-volume-test --import-from=dir:"$ARTIFACTS_PATH"/image-volume-test-image --add-name=docker.io/library/mrunalp/image-volume-test --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=busybox:latest --import-from=dir:"$ARTIFACTS_PATH"/busybox-image --add-name=docker.io/library/busybox:latest --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=runcom/stderr-test:latest --import-from=dir:"$ARTIFACTS_PATH"/stderr-test --add-name=docker.io/runcom/stderr-test:latest --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=docker.io/library/redis:alpine --import-from=dir:"$ARTIFACTS_PATH"/redis-image --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=docker.io/mrunalp/oom:latest --import-from=dir:"$ARTIFACTS_PATH"/oom-image --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=docker.io/mrunalp/image-volume-test:latest --import-from=dir:"$ARTIFACTS_PATH"/image-volume-test-image --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=docker.io/library/busybox:latest --import-from=dir:"$ARTIFACTS_PATH"/busybox-image --signature-policy="$INTEGRATION_ROOT"/policy.json
"$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name=docker.io/runcom/stderr-test:latest --import-from=dir:"$ARTIFACTS_PATH"/stderr-test --signature-policy="$INTEGRATION_ROOT"/policy.json
"$CRIO_BINARY" ${DEFAULT_MOUNTS_OPTS} ${HOOKS_OPTS} --conmon "$CONMON_BINARY" --listen "$CRIO_SOCKET" --cgroup-manager "$CGROUP_MANAGER" --registry "docker.io" --runtime "$RUNTIME_BINARY" --root "$TESTDIR/crio" --runroot "$TESTDIR/crio-run" $STORAGE_OPTIONS --seccomp-profile "$seccomp" --apparmor-profile "$apparmor" --cni-config-dir "$CRIO_CNI_CONFIG" --cni-plugin-dir "$CRIO_CNI_PLUGIN" --signature-policy "$INTEGRATION_ROOT"/policy.json --image-volumes "$IMAGE_VOLUMES" --pids-limit "$PIDS_LIMIT" --enable-shared-pid-namespace=${ENABLE_SHARED_PID_NAMESPACE} --log-size-max "$LOG_SIZE_MAX_LIMIT" --config /dev/null config >$CRIO_CONFIG
# Prepare the CNI configuration files, we're running with non host networking by default
@ -252,44 +234,28 @@ function start_crio() {
if [ "$status" -ne 0 ] ; then
crictl pull redis:alpine
fi
REDIS_IMAGEID=$(crictl inspecti redis:alpine | head -1 | sed -e "s/ID: //g")
REDIS_IMAGEID=$(crictl inspecti redis:alpine | grep ^ID: | head -n 1 | sed -e "s/ID: //g")
REDIS_IMAGEREF=$(crictl inspecti redis:alpine | grep ^Digest: | head -n 1 | sed -e "s/Digest: //g")
run crictl inspecti mrunalp/oom
if [ "$status" -ne 0 ] ; then
crictl pull mrunalp/oom
fi
#
#
#
# TODO: remove the code below for redis digested image id when
# https://github.com/kubernetes-incubator/cri-o/issues/531 is complete
# as the digested reference will be auto-stored when pulling the tag
# above
#
#
#
REDIS_IMAGEID_DIGESTED="redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b"
run crictl inspecti $REDIS_IMAGEID_DIGESTED
if [ "$status" -ne 0 ]; then
crictl pull $REDIS_IMAGEID_DIGESTED
fi
#
#
#
run crictl inspecti runcom/stderr-test
OOM_IMAGEID=$(crictl inspecti mrunalp/oom | grep ^ID: | head -n 1 | sed -e "s/ID: //g")
run crioctl image status --id=runcom/stderr-test
if [ "$status" -ne 0 ] ; then
crictl pull runcom/stderr-test:latest
fi
STDERR_IMAGEID=$(crictl inspecti runcom/stderr-test | head -1 | sed -e "s/ID: //g")
STDERR_IMAGEID=$(crictl inspecti runcom/stderr-test | grep ^ID: | head -n 1 | sed -e "s/ID: //g")
run crictl inspecti busybox
if [ "$status" -ne 0 ] ; then
crictl pull busybox:latest
fi
BUSYBOX_IMAGEID=$(crictl inspecti busybox | head -1 | sed -e "s/ID: //g")
BUSYBOX_IMAGEID=$(crictl inspecti busybox | grep ^ID: | head -n 1 | sed -e "s/ID: //g")
run crictl inspecti mrunalp/image-volume-test
if [ "$status" -ne 0 ] ; then
crictl pull mrunalp/image-volume-test:latest
fi
VOLUME_IMAGEID=$(crictl inspecti mrunalp/image-volume-test | head -1 | sed -e "s/ID: //g")
VOLUME_IMAGEID=$(crictl inspecti mrunalp/image-volume-test | grep ^ID: | head -n 1 | sed -e "s/ID: //g")
}
function cleanup_ctrs() {

View File

@ -20,12 +20,16 @@ function teardown() {
run crictl create "$pod_id" "$TESTDIR"/ctr_by_imageid.json "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
ctr_id="$output"
run crictl start "$ctr_id"
echo "$output"
[ "$status" -eq 0 ]
cleanup_ctrs
cleanup_pods
stop_crio
}
@test "container status return image:tag if created by image ID" {
@test "container status when created by image ID" {
start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
@ -43,16 +47,15 @@ function teardown() {
run crictl inspect "$ctr_id" --output yaml
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "image: redis:alpine" ]]
[[ "$output" =~ "image: docker.io/library/redis:alpine" ]]
[[ "$output" =~ "imageRef: $REDIS_IMAGEREF" ]]
cleanup_ctrs
cleanup_pods
stop_crio
}
@test "container status return image@digest if created by image ID and digest available" {
skip "depends on https://github.com/kubernetes-incubator/cri-o/issues/531"
@test "container status when created by image tagged reference" {
start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
@ -60,9 +63,9 @@ function teardown() {
[ "$status" -eq 0 ]
pod_id="$output"
sed -e "s/%VALUE%/$REDIS_IMAGEID_DIGESTED/g" "$TESTDATA"/container_config_by_imageid.json > "$TESTDIR"/ctr_by_imageid.json
sed -e "s/%VALUE%/redis:alpine/g" "$TESTDATA"/container_config_by_imageid.json > "$TESTDIR"/ctr_by_imagetag.json
run crictl create "$pod_id" "$TESTDIR"/ctr_by_imageid.json "$TESTDATA"/sandbox_config.json
run crictl create "$pod_id" "$TESTDIR"/ctr_by_imagetag.json "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
ctr_id="$output"
@ -70,22 +73,64 @@ function teardown() {
run crictl inspect "$ctr_id" --output yaml
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "image_ref: redis@sha256:03789f402b2ecfb98184bf128d180f398f81c63364948ff1454583b02442f73b" ]]
[[ "$output" =~ "image: docker.io/library/redis:alpine" ]]
[[ "$output" =~ "imageRef: $REDIS_IMAGEREF" ]]
cleanup_ctrs
cleanup_pods
stop_crio
}
@test "image pull" {
@test "container status when created by image canonical reference" {
start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
pod_id="$output"
sed -e "s|%VALUE%|$REDIS_IMAGEREF|g" "$TESTDATA"/container_config_by_imageid.json > "$TESTDIR"/ctr_by_imageref.json
run crictl create "$pod_id" "$TESTDIR"/ctr_by_imageref.json "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
ctr_id="$output"
run crictl start "$ctr_id"
echo "$output"
[ "$status" -eq 0 ]
run crictl inspect "$ctr_id" --output yaml
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "image: docker.io/library/redis:alpine" ]]
[[ "$output" =~ "imageRef: $REDIS_IMAGEREF" ]]
cleanup_ctrs
cleanup_pods
stop_crio
}
@test "image pull and list" {
start_crio "" "" --no-pause-image
run crictl pull "$IMAGE"
echo "$output"
[ "$status" -eq 0 ]
run crictl inspecti "$IMAGE"
run crictl images --quiet "$IMAGE"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
imageid="$output"
run crictl images @"$imageid"
[ "$status" -eq 0 ]
[[ "$output" =~ "$IMAGE" ]]
run crictl images --quiet "$imageid"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
cleanup_images
stop_crio
}
@ -108,7 +153,33 @@ function teardown() {
stop_crio
}
@test "image pull and list by digest" {
@test "image pull and list by tag and ID" {
start_crio "" "" --no-pause-image
run crictl pull "$IMAGE:go"
echo "$output"
[ "$status" -eq 0 ]
run crictl images --quiet "$IMAGE:go"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
imageid="$output"
run crictl images --quiet @"$imageid"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
run crictl images --quiet "$imageid"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
cleanup_images
stop_crio
}
@test "image pull and list by digest and ID" {
start_crio "" "" --no-pause-image
run crictl pull nginx@sha256:33eb1ed1e802d4f71e52421f56af028cdf12bb3bfff5affeaf5bf0e328ffa1bc
echo "$output"
@ -118,18 +189,14 @@ function teardown() {
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
imageid="$output"
run crictl images --quiet nginx@33eb1ed1e802d4f71e52421f56af028cdf12bb3bfff5affeaf5bf0e328ffa1bc
run crictl images --quiet @"$imageid"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
run crictl images --quiet @33eb1ed1e802d4f71e52421f56af028cdf12bb3bfff5affeaf5bf0e328ffa1bc
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
run crictl images --quiet 33eb1ed1e802d4f71e52421f56af028cdf12bb3bfff5affeaf5bf0e328ffa1bc
run crictl images --quiet "$imageid"
[ "$status" -eq 0 ]
echo "$output"
[ "$output" != "" ]
@ -198,7 +265,7 @@ function teardown() {
[ "$status" -eq 0 ]
[ "$output" != "" ]
printf '%s\n' "$output" | while IFS= read -r id; do
run crictl inspecti "$id"
run crictl images -v "$id"
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "" ]

75
test/image_remove.bats Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env bats
load helpers
IMAGE=docker.io/kubernetes/pause
function teardown() {
cleanup_test
}
@test "image remove with multiple names, by name" {
start_crio "" "" --no-pause-image
# Pull the image, giving it one name.
run crictl pull "$IMAGE"
echo "$output"
[ "$status" -eq 0 ]
# Add a second name to the image.
run "$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name="$IMAGE":latest --add-name="$IMAGE":othertag --signature-policy="$INTEGRATION_ROOT"/policy.json
echo "$output"
[ "$status" -eq 0 ]
# Get the list of image names and IDs.
run crictl images -v
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "" ]
# Cycle through each name, removing it by name. The image that we assigned a second
# name to should still be around when we get to removing its second name.
grep ^RepoTags: <<< "$output" | while read -r header tag ignored ; do
run crictl rmi "$tag"
echo "$output"
[ "$status" -eq 0 ]
done
# List all images and their names. There should be none now.
run crictl images --quiet
echo "$output"
[ "$status" -eq 0 ]
[ "$output" = "" ]
printf '%s\n' "$output" | while IFS= read -r id; do
echo "$id"
done
# All done.
cleanup_images
stop_crio
}
@test "image remove with multiple names, by ID" {
start_crio "" "" --no-pause-image
# Pull the image, giving it one name.
run crictl pull "$IMAGE"
echo "$output"
[ "$status" -eq 0 ]
# Add a second name to the image.
run "$COPYIMG_BINARY" --root "$TESTDIR/crio" $STORAGE_OPTIONS --runroot "$TESTDIR/crio-run" --image-name="$IMAGE":latest --add-name="$IMAGE":othertag --signature-policy="$INTEGRATION_ROOT"/policy.json
echo "$output"
[ "$status" -eq 0 ]
# Get the list of the image's names and its ID.
run crictl images -v "$IMAGE":latest
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "" ]
# Try to remove the image using its ID. That should succeed.
grep ^ID: <<< "$output" | while read -r header id ; do
run crictl rmi "$id"
echo "$output"
[ "$status" -eq 0 ]
done
# The image should be gone now.
run crictl images -v "$IMAGE"
echo "$output"
[ "$status" -eq 0 ]
[ "$output" = "" ]
# All done.
cleanup_images
stop_crio
}

View File

@ -55,7 +55,7 @@ function teardown() {
run crictl exec --sync "$ctr_id" touch /imagevolume/test_file
echo "$output"
[ "$status" -eq 0 ]
[ "$output" = "" ]
run crictl stops "$pod_id"
echo "$output"
[ "$status" -eq 0 ]

View File

@ -30,13 +30,15 @@ function teardown() {
out=`echo -e "GET /containers/$ctr_id HTTP/1.1\r\nHost: crio\r\n" | socat - UNIX-CONNECT:$CRIO_SOCKET`
echo "$out"
[[ "$out" =~ "\"sandbox\":\"$pod_id\"" ]]
[[ "$out" =~ "\"image\":\"redis:alpine\"" ]]
[[ "$out" =~ "\"image\":\"docker.io/library/redis:alpine\"" ]]
[[ "$out" =~ "\"image_ref\":\"$REDIS_IMAGEREF\"" ]]
run crictl inspect --output json "$ctr_id"
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "\"id\": \"$ctr_id\"" ]]
[[ "$output" =~ "\"image\": \"redis:alpine\"" ]]
[[ "$output" =~ "\"image\": \"docker.io/library/redis:alpine\"" ]]
[[ "$output" =~ "\"imageRef\": \"$REDIS_IMAGEREF\"" ]]
run crictl inspects --output json "$pod_id"
echo "$output"

View File

@ -6,8 +6,8 @@ function teardown() {
cleanup_test
}
@test "pod disable shared pid namespace" {
ENABLE_SHARED_PID_NAMESPACE="false" start_crio
function pid_namespace_test() {
start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
echo "$output"
@ -23,7 +23,7 @@ function teardown() {
run crictl exec --sync "$ctr_id" cat /proc/1/cmdline
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "redis" ]]
[[ "$output" =~ "${EXPECTED_INIT:-redis}" ]]
run crictl stops "$pod_id"
echo "$output"
@ -36,32 +36,10 @@ function teardown() {
stop_crio
}
@test "pod disable shared pid namespace" {
ENABLE_SHARED_PID_NAMESPACE=false pid_namespace_test
}
@test "pod enable shared pid namespace" {
ENABLE_SHARED_PID_NAMESPACE="true" start_crio
run crictl runs "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
pod_id="$output"
run crictl create "$pod_id" "$TESTDATA"/container_redis.json "$TESTDATA"/sandbox_config.json
echo "$output"
[ "$status" -eq 0 ]
ctr_id="$output"
run crictl start "$ctr_id"
[ "$status" -eq 0 ]
run crictl exec --sync "$ctr_id" cat /proc/1/cmdline
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "pause" ]]
run crictl stops "$pod_id"
echo "$output"
[ "$status" -eq 0 ]
run crictl rms "$pod_id"
echo "$output"
[ "$status" -eq 0 ]
cleanup_ctrs
cleanup_pods
stop_crio
ENABLE_SHARED_PID_NAMESPACE=true EXPECTED_INIT=pause pid_namespace_test
}

View File

@ -49,7 +49,9 @@
"cpu_period": 10000,
"cpu_quota": 20000,
"cpu_shares": 512,
"oom_score_adj": 30
"oom_score_adj": 30,
"cpuset_cpus": "0-1",
"cpuset_mems": "0"
},
"security_context": {
"capabilities": {

View File

@ -138,32 +138,12 @@ make
sudo make install
```
Output:
```
install -D -m 755 crio /usr/local/bin/crio
install -D -m 755 conmon/conmon /usr/local/libexec/crio/conmon
install -D -m 755 pause/pause /usr/local/libexec/crio/pause
install -d -m 755 /usr/local/share/man/man{1,5,8}
install -m 644 docs/crio.conf.5 -t /usr/local/share/man/man5
install -m 644 docs/crio.8 -t /usr/local/share/man/man8
install -D -m 644 crio.conf /etc/crio/crio.conf
install -D -m 644 seccomp.json /etc/crio/seccomp.json
```
If you are installing for the first time, generate config as follows:
If you are installing for the first time, generate and install configuration files with:
```
sudo make install.config
```
Output:
```
install -D -m 644 crio.conf /etc/crio/crio.conf
install -D -m 644 seccomp.json /etc/crio/seccomp.json
```
#### Start the crio system daemon
```
@ -320,15 +300,15 @@ cd $GOPATH/src/github.com/kubernetes-incubator/cri-o
Next create the Pod and capture the Pod ID for later use:
```
POD_ID=$(sudo crictl runs test/testdata/sandbox_config.json)
POD_ID=$(sudo crictl runp test/testdata/sandbox_config.json)
```
> sudo crictl runs test/testdata/sandbox_config.json
> sudo crictl runp test/testdata/sandbox_config.json
Use the `crictl` command to get the status of the Pod:
```
sudo crictl inspects --output table $POD_ID
sudo crictl inspectp --output table $POD_ID
```
Output:

View File

@ -5,6 +5,7 @@ type ContainerInfo struct {
Name string `json:"name"`
Pid int `json:"pid"`
Image string `json:"image"`
ImageRef string `json:"image_ref"`
CreatedTime int64 `json:"created_time"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`

View File

@ -1,10 +1,10 @@
k8s.io/kubernetes v1.9.0-alpha.2 https://github.com/kubernetes/kubernetes
k8s.io/kubernetes a48f11c2257d84b0bec89864025508b0ef626b4f https://github.com/kubernetes/kubernetes
k8s.io/client-go master https://github.com/kubernetes/client-go
k8s.io/apimachinery master https://github.com/kubernetes/apimachinery
k8s.io/apiserver master https://github.com/kubernetes/apiserver
k8s.io/utils 4fe312863be2155a7b68acd2aff1c9221b24e68c https://github.com/kubernetes/utils
k8s.io/api master https://github.com/kubernetes/api
k8s.io/kube-openapi abfc5fbe1cf87ee697db107fdfd24c32fe4397a8 https://github.com/kubernetes/kube-openapi
k8s.io/kube-openapi 39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1 https://github.com/kubernetes/kube-openapi
k8s.io/apiextensions-apiserver master https://github.com/kubernetes/apiextensions-apiserver
#
github.com/googleapis/gnostic 0c5108395e2debce0d731cf0287ddf7242066aba
@ -12,15 +12,15 @@ github.com/gregjones/httpcache 787624de3eb7bd915c329cba748687a3b22666a6
github.com/json-iterator/go 1.0.0
github.com/peterbourgon/diskv v2.0.1
github.com/sirupsen/logrus v1.0.0
github.com/containers/image 57b257d128d6075ea3287991ee408d24c7bd2758
github.com/containers/image 3d0304a02154dddc8f97cc833aa0861cea5e9ade
github.com/docker/docker-credential-helpers d68f9aeca33f5fd3f08eeae5e9d175edf4e731d1
github.com/ostreedev/ostree-go master
github.com/containers/storage d7921c6facc516358070a1306689eda18adaa20a
github.com/containers/storage 0d32dfce498e06c132c60dac945081bf44c22464
github.com/containernetworking/cni v0.4.0
google.golang.org/grpc v1.0.4 https://github.com/grpc/grpc-go
github.com/opencontainers/selinux b29023b86e4a69d1b46b7e7b4e2b6fda03f0b9cd
github.com/opencontainers/go-digest v1.0.0-rc0
github.com/opencontainers/runtime-tools d3f7e9e9e631c7e87552d67dc7c86de33c3fb68a
github.com/opencontainers/runtime-tools 625e2322645b151a7cbb93a8b42920933e72167f
github.com/opencontainers/runc 45bde006ca8c90e089894508708bcf0e2cdf9e13
github.com/mrunalp/fileutils master
github.com/vishvananda/netlink master
@ -113,3 +113,6 @@ github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac
github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
github.com/pmezard/go-difflib v1.0.0
github.com/xeipuuv/gojsonreference master
github.com/xeipuuv/gojsonschema master
github.com/xeipuuv/gojsonpointer master

View File

@ -12,8 +12,6 @@ import (
"strings"
"time"
pb "gopkg.in/cheggaaa/pb.v1"
"github.com/containers/image/image"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/signature"
@ -22,6 +20,7 @@ import (
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
pb "gopkg.in/cheggaaa/pb.v1"
)
type digestingReader struct {
@ -31,23 +30,6 @@ type digestingReader struct {
validationFailed bool
}
// imageCopier allows us to keep track of diffID values for blobs, and other
// data, that we're copying between images, and cache other information that
// might allow us to take some shortcuts
type imageCopier struct {
copiedBlobs map[digest.Digest]digest.Digest
cachedDiffIDs map[digest.Digest]digest.Digest
manifestUpdates *types.ManifestUpdateOptions
dest types.ImageDestination
src types.Image
rawSource types.ImageSource
diffIDsAreNeeded bool
canModifyManifest bool
reportWriter io.Writer
progressInterval time.Duration
progress chan types.ProgressProperties
}
// newDigestingReader returns an io.Reader implementation with contents of source, which will eventually return a non-EOF error
// and set validationFailed to true if the source stream does not match expectedDigest.
func newDigestingReader(source io.Reader, expectedDigest digest.Digest) (*digestingReader, error) {
@ -86,6 +68,27 @@ func (d *digestingReader) Read(p []byte) (int, error) {
return n, err
}
// 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
reportWriter io.Writer
progressInterval time.Duration
progress chan types.ProgressProperties
}
// imageCopier tracks state specific to a single image (possibly an item of a manifest list)
type imageCopier struct {
c *copier
manifestUpdates *types.ManifestUpdateOptions
src types.Image
diffIDsAreNeeded bool
canModifyManifest bool
}
// Options allows supplying non-default configuration modifying the behavior of CopyImage.
type Options struct {
RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature.
@ -95,6 +98,8 @@ type Options struct {
DestinationCtx *types.SystemContext
ProgressInterval time.Duration // time to wait between reports to signal the progress channel
Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset.
// manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type
ForceManifestMIMEType string
}
// Image copies image from srcRef to destRef, using policyContext to validate
@ -115,10 +120,6 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
reportWriter = options.ReportWriter
}
writeReport := func(f string, a ...interface{}) {
fmt.Fprintf(reportWriter, f, a...)
}
dest, err := destRef.NewImageDestination(options.DestinationCtx)
if err != nil {
return errors.Wrapf(err, "Error initializing destination %s", transports.ImageName(destRef))
@ -133,43 +134,89 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
if err != nil {
return errors.Wrapf(err, "Error initializing source %s", transports.ImageName(srcRef))
}
unparsedImage := image.UnparsedFromSource(rawSource)
defer func() {
if unparsedImage != nil {
if err := unparsedImage.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (unparsed: %v)", err)
}
if err := rawSource.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (src: %v)", err)
}
}()
c := &copier{
copiedBlobs: make(map[digest.Digest]digest.Digest),
cachedDiffIDs: make(map[digest.Digest]digest.Digest),
dest: dest,
rawSource: rawSource,
reportWriter: reportWriter,
progressInterval: options.ProgressInterval,
progress: options.Progress,
}
unparsedToplevel := image.UnparsedInstance(rawSource, nil)
multiImage, err := isMultiImage(unparsedToplevel)
if err != nil {
return 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
}
} 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)
if err != nil {
return 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 err := c.dest.Commit(); err != nil {
return errors.Wrap(err, "Error committing the finished image")
}
return 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) {
// 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)
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()))
}
if multiImage {
return 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")
}
src, err := image.FromUnparsedImage(unparsedImage)
src, err := image.FromUnparsedImage(options.SourceCtx, unparsedImage)
if err != nil {
return errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(srcRef))
return errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
}
unparsedImage = nil
defer func() {
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (source: %v)", err)
}
}()
if err := checkImageDestinationForCurrentRuntimeOS(src, dest); err != nil {
if err := checkImageDestinationForCurrentRuntimeOS(options.DestinationCtx, src, c.dest); err != nil {
return err
}
if src.IsMultiImage() {
return errors.Errorf("can not copy %s: manifest contains multiple images", transports.ImageName(srcRef))
}
var sigs [][]byte
if options.RemoveSignatures {
sigs = [][]byte{}
} else {
writeReport("Getting image source signatures\n")
c.Printf("Getting image source signatures\n")
s, err := src.Signatures(context.TODO())
if err != nil {
return errors.Wrap(err, "Error reading signatures")
@ -177,41 +224,33 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
sigs = s
}
if len(sigs) != 0 {
writeReport("Checking if image destination supports signatures\n")
if err := dest.SupportsSignatures(); err != nil {
c.Printf("Checking if image destination supports signatures\n")
if err := c.dest.SupportsSignatures(); err != nil {
return errors.Wrap(err, "Can not copy signatures")
}
}
canModifyManifest := len(sigs) == 0
manifestUpdates := types.ManifestUpdateOptions{}
manifestUpdates.InformationOnly.Destination = dest
ic := imageCopier{
c: c,
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
src: src,
// diffIDsAreNeeded is computed later
canModifyManifest: len(sigs) == 0,
}
if err := updateEmbeddedDockerReference(&manifestUpdates, dest, src, canModifyManifest); err != nil {
if err := ic.updateEmbeddedDockerReference(); err != nil {
return 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 := determineManifestConversion(&manifestUpdates, src, dest.SupportedManifestMIMETypes(), canModifyManifest)
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := ic.determineManifestConversion(c.dest.SupportedManifestMIMETypes(), options.ForceManifestMIMEType)
if err != nil {
return err
}
// If src.UpdatedImageNeedsLayerDiffIDs(manifestUpdates) will be true, it needs to be true by the time we get here.
ic := imageCopier{
copiedBlobs: make(map[digest.Digest]digest.Digest),
cachedDiffIDs: make(map[digest.Digest]digest.Digest),
manifestUpdates: &manifestUpdates,
dest: dest,
src: src,
rawSource: rawSource,
diffIDsAreNeeded: src.UpdatedImageNeedsLayerDiffIDs(manifestUpdates),
canModifyManifest: canModifyManifest,
reportWriter: reportWriter,
progressInterval: options.ProgressInterval,
progress: options.Progress,
}
// 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
@ -233,9 +272,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
}
// 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 !canModifyManifest, that would just be a string of repeated failures for the same reason,
// With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason,
// so lets bail out early and with a better error message.
if !canModifyManifest {
if !ic.canModifyManifest {
return errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
}
@ -243,7 +282,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
errs := []string{fmt.Sprintf("%s(%v)", preferredManifestMIMEType, err)}
for _, manifestMIMEType := range otherManifestMIMETypeCandidates {
logrus.Debugf("Trying to use manifest type %s…", manifestMIMEType)
manifestUpdates.ManifestMIMEType = manifestMIMEType
ic.manifestUpdates.ManifestMIMEType = manifestMIMEType
attemptedManifest, err := ic.copyUpdatedConfigAndManifest()
if err != nil {
logrus.Debugf("Upload of manifest type %s failed: %v", manifestMIMEType, err)
@ -262,35 +301,44 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
}
if options.SignBy != "" {
newSig, err := createSignature(dest, manifest, options.SignBy, reportWriter)
newSig, err := c.createSignature(manifest, options.SignBy)
if err != nil {
return err
}
sigs = append(sigs, newSig)
}
writeReport("Storing signatures\n")
if err := dest.PutSignatures(sigs); err != nil {
c.Printf("Storing signatures\n")
if err := c.dest.PutSignatures(sigs); err != nil {
return errors.Wrap(err, "Error writing signatures")
}
if err := dest.Commit(); err != nil {
return errors.Wrap(err, "Error committing the finished image")
}
return nil
}
func checkImageDestinationForCurrentRuntimeOS(src types.Image, dest types.ImageDestination) error {
// Printf writes a formatted string to c.reportWriter.
// Note that the method name Printf is not entirely arbitrary: (go tool vet)
// has a built-in list of functions/methods (whatever object they are for)
// which have their format strings checked; for other names we would have
// to pass a parameter to every (go tool vet) invocation.
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 {
if dest.MustMatchRuntimeOS() {
wantedOS := runtime.GOOS
if ctx != nil && ctx.OSChoice != "" {
wantedOS = ctx.OSChoice
}
c, err := src.OCIConfig()
if err != nil {
return errors.Wrapf(err, "Error parsing image configuration")
}
osErr := fmt.Errorf("image operating system %q cannot be used on %q", c.OS, runtime.GOOS)
if runtime.GOOS == "windows" && c.OS == "linux" {
osErr := fmt.Errorf("image operating system %q cannot be used on %q", c.OS, wantedOS)
if wantedOS == "windows" && c.OS == "linux" {
return osErr
} else if runtime.GOOS != "windows" && c.OS == "windows" {
} else if wantedOS != "windows" && c.OS == "windows" {
return osErr
}
}
@ -298,35 +346,44 @@ func checkImageDestinationForCurrentRuntimeOS(src types.Image, dest types.ImageD
}
// updateEmbeddedDockerReference handles the Docker reference embedded in Docker schema1 manifests.
func updateEmbeddedDockerReference(manifestUpdates *types.ManifestUpdateOptions, dest types.ImageDestination, src types.Image, canModifyManifest bool) error {
destRef := dest.Reference().DockerReference()
func (ic *imageCopier) updateEmbeddedDockerReference() error {
destRef := ic.c.dest.Reference().DockerReference()
if destRef == nil {
return nil // Destination does not care about Docker references
}
if !src.EmbeddedDockerReferenceConflicts(destRef) {
if !ic.src.EmbeddedDockerReferenceConflicts(destRef) {
return nil // No reference embedded in the manifest, or it matches destRef already.
}
if !canModifyManifest {
if !ic.canModifyManifest {
return errors.Errorf("Copying a schema1 image with an embedded Docker reference to %s (Docker reference %s) would invalidate existing signatures. Explicitly enable signature removal to proceed anyway",
transports.ImageName(dest.Reference()), destRef.String())
transports.ImageName(ic.c.dest.Reference()), destRef.String())
}
manifestUpdates.EmbeddedDockerReference = destRef
ic.manifestUpdates.EmbeddedDockerReference = destRef
return nil
}
// copyLayers copies layers from src/rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest.
// 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 {
srcInfos := ic.src.LayerInfos()
destInfos := []types.BlobInfo{}
diffIDs := []digest.Digest{}
updatedSrcInfos := ic.src.LayerInfosForCopy()
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
diffID digest.Digest
err error
)
if ic.dest.AcceptsForeignLayerURLs() && len(srcLayer.URLs) != 0 {
if ic.c.dest.AcceptsForeignLayerURLs() && len(srcLayer.URLs) != 0 {
// DiffIDs are, currently, needed only when converting from schema1.
// In which case src.LayerInfos will not have URLs because schema1
// does not support them.
@ -334,7 +391,7 @@ func (ic *imageCopier) copyLayers() error {
return errors.New("getting DiffID for foreign layers is unimplemented")
}
destInfo = srcLayer
fmt.Fprintf(ic.reportWriter, "Skipping foreign layer %q copy to %s\n", destInfo.Digest, ic.dest.Reference().Transport().Name())
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)
if err != nil {
@ -348,7 +405,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
@ -379,7 +436,7 @@ func (ic *imageCopier) copyUpdatedConfigAndManifest() ([]byte, error) {
// We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion.
// So, this can only happen if we are trying to upload using one of the other MIME type candidates.
// Because UpdatedImageNeedsLayerDiffIDs is true only when converting from s1 to s2, this case should only arise
// when ic.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2.
// when ic.c.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2.
// Supposedly s2-only registries do not exist or are extremely rare, so failing with this error message is good enough for now.
// 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)
@ -395,27 +452,27 @@ func (ic *imageCopier) copyUpdatedConfigAndManifest() ([]byte, error) {
return nil, errors.Wrap(err, "Error reading manifest")
}
if err := ic.copyConfig(pendingImage); err != nil {
if err := ic.c.copyConfig(pendingImage); err != nil {
return nil, err
}
fmt.Fprintf(ic.reportWriter, "Writing manifest to image destination\n")
if err := ic.dest.PutManifest(manifest); err != nil {
ic.c.Printf("Writing manifest to image destination\n")
if err := ic.c.dest.PutManifest(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 (ic *imageCopier) copyConfig(src types.Image) error {
func (c *copier) copyConfig(src types.Image) error {
srcInfo := src.ConfigInfo()
if srcInfo.Digest != "" {
fmt.Fprintf(ic.reportWriter, "Copying config %s\n", srcInfo.Digest)
c.Printf("Copying config %s\n", srcInfo.Digest)
configBlob, err := src.ConfigBlob()
if err != nil {
return errors.Wrapf(err, "Error reading config blob %s", srcInfo.Digest)
}
destInfo, err := ic.copyBlobFromStream(bytes.NewReader(configBlob), srcInfo, nil, false)
destInfo, err := c.copyBlobFromStream(bytes.NewReader(configBlob), srcInfo, nil, false)
if err != nil {
return err
}
@ -437,12 +494,12 @@ type diffIDResult struct {
// 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) {
// Check if we already have a blob with this digest
haveBlob, extantBlobSize, err := ic.dest.HasBlob(srcInfo)
haveBlob, extantBlobSize, err := ic.c.dest.HasBlob(srcInfo)
if err != nil {
return types.BlobInfo{}, "", errors.Wrapf(err, "Error checking for blob %s at destination", srcInfo.Digest)
}
// If we already have a cached diffID for this blob, we don't need to compute it
diffIDIsNeeded := ic.diffIDsAreNeeded && (ic.cachedDiffIDs[srcInfo.Digest] == "")
diffIDIsNeeded := ic.diffIDsAreNeeded && (ic.c.cachedDiffIDs[srcInfo.Digest] == "")
// If we already have the blob, and we don't need to recompute the diffID, then we might be able to avoid reading it again
if haveBlob && !diffIDIsNeeded {
// Check the blob sizes match, if we were given a size this time
@ -451,17 +508,17 @@ 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.dest.ReapplyBlob(srcInfo)
blobinfo, err := ic.c.dest.ReapplyBlob(srcInfo)
if err != nil {
return types.BlobInfo{}, "", errors.Wrapf(err, "Error reapplying blob %s at destination", srcInfo.Digest)
}
fmt.Fprintf(ic.reportWriter, "Skipping fetch of repeat blob %s\n", srcInfo.Digest)
return blobinfo, ic.cachedDiffIDs[srcInfo.Digest], err
ic.c.Printf("Skipping fetch of repeat blob %s\n", srcInfo.Digest)
return blobinfo, ic.c.cachedDiffIDs[srcInfo.Digest], err
}
// Fallback: copy the layer, computing the diffID if we need to do so
fmt.Fprintf(ic.reportWriter, "Copying blob %s\n", srcInfo.Digest)
srcStream, srcBlobSize, err := ic.rawSource.GetBlob(srcInfo)
ic.c.Printf("Copying blob %s\n", srcInfo.Digest)
srcStream, srcBlobSize, err := ic.c.rawSource.GetBlob(srcInfo)
if err != nil {
return types.BlobInfo{}, "", errors.Wrapf(err, "Error reading blob %s", srcInfo.Digest)
}
@ -479,7 +536,7 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest
return types.BlobInfo{}, "", errors.Wrap(diffIDResult.err, "Error computing layer DiffID")
}
logrus.Debugf("Computed DiffID %s for layer %s", diffIDResult.digest, srcInfo.Digest)
ic.cachedDiffIDs[srcInfo.Digest] = diffIDResult.digest
ic.c.cachedDiffIDs[srcInfo.Digest] = diffIDResult.digest
}
return blobInfo, diffIDResult.digest, nil
}
@ -513,7 +570,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl
return pipeWriter
}
}
blobInfo, err := ic.copyBlobFromStream(srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest) // Sets err to nil on success
blobInfo, err := ic.c.copyBlobFromStream(srcStream, srcInfo, getDiffIDRecorder, ic.canModifyManifest) // 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
}
@ -547,7 +604,7 @@ 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 (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
func (c *copier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo,
getOriginalLayerCopyWriter func(decompressor compression.DecompressorFunc) io.Writer,
canCompress bool) (types.BlobInfo, error) {
// The copying happens through a pipeline of connected io.Readers.
@ -575,7 +632,7 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo
// === Report progress using a pb.Reader.
bar := pb.New(int(srcInfo.Size)).SetUnits(pb.U_BYTES)
bar.Output = ic.reportWriter
bar.Output = c.reportWriter
bar.SetMaxWidth(80)
bar.ShowTimeLeft = false
bar.ShowPercent = false
@ -592,7 +649,7 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo
// === Compress the layer if it is uncompressed and compression is desired
var inputInfo types.BlobInfo
if !canCompress || isCompressed || !ic.dest.ShouldCompressLayers() {
if !canCompress || isCompressed || !c.dest.ShouldCompressLayers() {
logrus.Debugf("Using original blob without modification")
inputInfo = srcInfo
} else {
@ -609,19 +666,19 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo
inputInfo.Size = -1
}
// === Report progress using the ic.progress channel, if required.
if ic.progress != nil && ic.progressInterval > 0 {
// === Report progress using the c.progress channel, if required.
if c.progress != nil && c.progressInterval > 0 {
destStream = &progressReader{
source: destStream,
channel: ic.progress,
interval: ic.progressInterval,
channel: c.progress,
interval: c.progressInterval,
artifact: srcInfo,
lastTime: time.Now(),
}
}
// === Finally, send the layer stream to dest.
uploadedInfo, err := ic.dest.PutBlob(destStream, inputInfo)
uploadedInfo, err := c.dest.PutBlob(destStream, inputInfo)
if err != nil {
return types.BlobInfo{}, errors.Wrap(err, "Error writing blob")
}

View File

@ -37,16 +37,20 @@ func (os *orderedSet) append(s string) {
}
}
// determineManifestConversion updates manifestUpdates to convert manifest to a supported MIME type, if necessary and canModifyManifest.
// Note that the conversion will only happen later, through src.UpdatedImage
// determineManifestConversion updates ic.manifestUpdates to convert manifest to a supported MIME type, if necessary and ic.canModifyManifest.
// 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 determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, src types.Image, destSupportedManifestMIMETypes []string, canModifyManifest bool) (string, []string, error) {
_, srcType, err := src.Manifest()
func (ic *imageCopier) determineManifestConversion(destSupportedManifestMIMETypes []string, forceManifestMIMEType string) (string, []string, error) {
_, srcType, err := ic.src.Manifest()
if err != nil { // This should have been cached?!
return "", nil, errors.Wrap(err, "Error reading manifest")
}
if forceManifestMIMEType != "" {
destSupportedManifestMIMETypes = []string{forceManifestMIMEType}
}
if len(destSupportedManifestMIMETypes) == 0 {
return srcType, []string{}, nil // Anything goes; just use the original as is, do not try any conversions.
}
@ -67,10 +71,10 @@ func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, s
if _, ok := supportedByDest[srcType]; ok {
prioritizedTypes.append(srcType)
}
if !canModifyManifest {
// We could also drop the !canModifyManifest parameter and have the caller
if !ic.canModifyManifest {
// We could also drop the !ic.canModifyManifest check and have the caller
// make the choice; it is already doing that to an extent, to improve error
// messages. But it is nice to hide the “if !canModifyManifest, do no conversion”
// messages. But it is nice to hide the “if !ic.canModifyManifest, do no conversion”
// special case in here; the caller can then worry (or not) only about a good UI.
logrus.Debugf("We can't modify the manifest, hoping for the best...")
return srcType, []string{}, nil // Take our chances - FIXME? Or should we fail without trying?
@ -94,9 +98,18 @@ func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, s
}
preferredType := prioritizedTypes.list[0]
if preferredType != srcType {
manifestUpdates.ManifestMIMEType = preferredType
ic.manifestUpdates.ManifestMIMEType = preferredType
} else {
logrus.Debugf("... will first try using the original manifest unmodified")
}
return preferredType, prioritizedTypes.list[1:], nil
}
// isMultiImage returns true if img is a list of images
func isMultiImage(img types.UnparsedImage) (bool, error) {
_, mt, err := img.Manifest()
if err != nil {
return false, err
}
return manifest.MIMETypeIsMultiImage(mt), nil
}

View File

@ -1,17 +1,13 @@
package copy
import (
"fmt"
"io"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/pkg/errors"
)
// createSignature creates a new signature of manifest at (identified by) dest using keyIdentity.
func createSignature(dest types.ImageDestination, manifest []byte, keyIdentity string, reportWriter io.Writer) ([]byte, error) {
// createSignature creates a new signature of manifest using keyIdentity.
func (c *copier) createSignature(manifest []byte, keyIdentity string) ([]byte, error) {
mech, err := signature.NewGPGSigningMechanism()
if err != nil {
return nil, errors.Wrap(err, "Error initializing GPG")
@ -21,12 +17,12 @@ func createSignature(dest types.ImageDestination, manifest []byte, keyIdentity s
return nil, errors.Wrap(err, "Signing not supported")
}
dockerReference := dest.Reference().DockerReference()
dockerReference := c.dest.Reference().DockerReference()
if dockerReference == nil {
return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(dest.Reference()))
return nil, errors.Errorf("Cannot determine canonical Docker reference for destination %s", transports.ImageName(c.dest.Reference()))
}
fmt.Fprintf(reportWriter, "Signing manifest\n")
c.Printf("Signing manifest\n")
newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, keyIdentity)
if err != nil {
return nil, errors.Wrap(err, "Error creating signature")

View File

@ -4,19 +4,77 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/containers/image/types"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const version = "Directory Transport Version: 1.0\n"
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
// using the 'dir' transport
var ErrNotContainerImageDir = errors.New("not a containers image directory, don't want to overwrite important data")
type dirImageDestination struct {
ref dirReference
ref dirReference
compress bool
}
// newImageDestination returns an ImageDestination for writing to an existing directory.
func newImageDestination(ref dirReference) types.ImageDestination {
return &dirImageDestination{ref}
// newImageDestination returns an ImageDestination for writing to a directory.
func newImageDestination(ref dirReference, compress bool) (types.ImageDestination, error) {
d := &dirImageDestination{ref: ref, compress: compress}
// If directory exists check if it is empty
// if not empty, check whether the contents match that of a container image directory and overwrite the contents
// if the contents don't match throw an error
dirExists, err := pathExists(d.ref.resolvedPath)
if err != nil {
return nil, errors.Wrapf(err, "error checking for path %q", d.ref.resolvedPath)
}
if dirExists {
isEmpty, err := isDirEmpty(d.ref.resolvedPath)
if err != nil {
return nil, err
}
if !isEmpty {
versionExists, err := pathExists(d.ref.versionPath())
if err != nil {
return nil, errors.Wrapf(err, "error checking if path exists %q", d.ref.versionPath())
}
if versionExists {
contents, err := ioutil.ReadFile(d.ref.versionPath())
if err != nil {
return nil, err
}
// check if contents of version file is what we expect it to be
if string(contents) != version {
return nil, ErrNotContainerImageDir
}
} else {
return nil, ErrNotContainerImageDir
}
// delete directory contents so that only one image is in the directory at a time
if err = removeDirContents(d.ref.resolvedPath); err != nil {
return nil, errors.Wrapf(err, "error erasing contents in %q", d.ref.resolvedPath)
}
logrus.Debugf("overwriting existing container image directory %q", d.ref.resolvedPath)
}
} else {
// create directory if it doesn't exist
if err := os.MkdirAll(d.ref.resolvedPath, 0755); err != nil {
return nil, errors.Wrapf(err, "unable to create directory %q", d.ref.resolvedPath)
}
}
// create version file
err = ioutil.WriteFile(d.ref.versionPath(), []byte(version), 0755)
if err != nil {
return nil, errors.Wrapf(err, "error creating version file %q", d.ref.versionPath())
}
return d, nil
}
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
@ -42,7 +100,7 @@ func (d *dirImageDestination) SupportsSignatures() error {
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
func (d *dirImageDestination) ShouldCompressLayers() bool {
return false
return d.compress
}
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
@ -147,3 +205,39 @@ func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
func (d *dirImageDestination) Commit() error {
return nil
}
// returns true if path exists
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if err != nil && os.IsNotExist(err) {
return false, nil
}
return false, err
}
// returns true if directory is empty
func isDirEmpty(path string) (bool, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return false, err
}
return len(files) == 0, nil
}
// deletes the contents of a directory
func removeDirContents(path string) error {
files, err := ioutil.ReadDir(path)
if err != nil {
return err
}
for _, file := range files {
if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil {
return err
}
}
return nil
}

View File

@ -35,7 +35,12 @@ func (s *dirImageSource) Close() error {
// 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 *dirImageSource) GetManifest() ([]byte, string, error) {
// 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) {
if instanceDigest != nil {
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
}
m, err := ioutil.ReadFile(s.ref.manifestPath())
if err != nil {
return nil, "", err
@ -43,10 +48,6 @@ func (s *dirImageSource) GetManifest() ([]byte, string, error) {
return m, manifest.GuessMIMEType(m), err
}
func (s *dirImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
r, err := os.Open(s.ref.layerPath(info.Digest))
@ -60,7 +61,14 @@ func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, err
return r, fi.Size(), nil
}
func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (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) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if instanceDigest != nil {
return nil, errors.Errorf(`Manifests lists are not supported by "dir:"`)
}
signatures := [][]byte{}
for i := 0; ; i++ {
signature, err := ioutil.ReadFile(s.ref.signaturePath(i))
@ -74,3 +82,8 @@ func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
}
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() []types.BlobInfo {
return nil
}

View File

@ -134,13 +134,14 @@ func (ref dirReference) PolicyConfigurationNamespaces() []string {
return res
}
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned Image.
// 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.
func (ref dirReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// 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) {
src := newImageSource(ref)
return image.FromSource(src)
return image.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.
@ -152,7 +153,11 @@ func (ref dirReference) NewImageSource(ctx *types.SystemContext) (types.ImageSou
// 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) {
return newImageDestination(ref), nil
compress := false
if ctx != nil {
compress = ctx.DirForceCompress
}
return newImageDestination(ref, compress)
}
// DeleteImage deletes the named image from the registry, if supported.
@ -175,3 +180,8 @@ func (ref dirReference) layerPath(digest digest.Digest) string {
func (ref dirReference) signaturePath(index int) string {
return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1))
}
// versionPath returns a path for the version file within a directory using our conventions.
func (ref dirReference) versionPath() string {
return filepath.Join(ref.path, "version")
}

View File

@ -34,3 +34,8 @@ func (s *archiveImageSource) Reference() types.ImageReference {
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() []types.BlobInfo {
return nil
}

View File

@ -125,13 +125,14 @@ func (ref archiveReference) PolicyConfigurationNamespaces() []string {
return []string{}
}
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned Image.
// 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.
func (ref archiveReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// 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(src)
return ctrImage.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.

View File

@ -0,0 +1,69 @@
package daemon
import (
"net/http"
"path/filepath"
"github.com/containers/image/types"
dockerclient "github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
)
const (
// The default API version to be used in case none is explicitly specified
defaultAPIVersion = "1.22"
)
// NewDockerClient initializes a new API client based on the passed SystemContext.
func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
host := dockerclient.DefaultDockerHost
if ctx != nil && ctx.DockerDaemonHost != "" {
host = ctx.DockerDaemonHost
}
// Sadly, unix:// sockets don't work transparently with dockerclient.NewClient.
// They work fine with a nil httpClient; with a non-nil httpClient, the transports
// TLSClientConfig must be nil (or the client will try using HTTPS over the PF_UNIX socket
// 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)
if err != nil {
return nil, err
}
var httpClient *http.Client
if proto != "unix" {
hc, err := tlsConfig(ctx)
if err != nil {
return nil, err
}
httpClient = hc
}
return dockerclient.NewClient(host, defaultAPIVersion, httpClient, nil)
}
func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
options := tlsconfig.Options{}
if ctx != nil && ctx.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")
}
tlsc, err := tlsconfig.Client(options)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
CheckRedirect: dockerclient.CheckRedirect,
}, nil
}

View File

@ -14,6 +14,7 @@ import (
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(systemCtx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
func newImageDestination(ctx *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(systemCtx *types.SystemContext, ref daemonReference) (t
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
}
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
var mustMatchRuntimeOS = true
if ctx != nil && ctx.DockerDaemonHost != client.DefaultDockerHost {
mustMatchRuntimeOS = false
}
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
@ -42,16 +48,17 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
// 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)
ctx, goroutineCancel := context.WithCancel(context.Background())
go imageLoadGoroutine(ctx, c, reader, statusChannel)
goroutineContext, goroutineCancel := context.WithCancel(context.Background())
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
}
@ -80,7 +87,7 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
// 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.

View File

@ -6,14 +6,12 @@ import (
"os"
"github.com/containers/image/docker/tarfile"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/types"
"github.com/docker/docker/client"
"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
@ -35,7 +33,7 @@ type layerInfo struct {
// 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 := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
@ -48,7 +46,7 @@ func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageS
defer inputStream.Close()
// FIXME: use SystemContext here.
tarCopyFile, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-daemon-tar")
tarCopyFile, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-daemon-tar")
if err != nil {
return nil, err
}
@ -83,3 +81,8 @@ func (s *daemonImageSource) Reference() types.ImageReference {
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() []types.BlobInfo {
return nil
}

View File

@ -151,14 +151,17 @@ func (ref daemonReference) PolicyConfigurationNamespaces() []string {
return []string{}
}
// NewImage returns a types.Image for this reference.
// The caller must call .Close() on the returned Image.
func (ref daemonReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// 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 daemonReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
src, err := newImageSource(ctx, ref)
if err != nil {
return nil, err
}
return image.FromSource(src)
return image.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.

View File

@ -8,7 +8,6 @@ import (
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@ -125,69 +124,6 @@ func dockerCertDir(ctx *types.SystemContext, hostPort string) string {
return filepath.Join(hostCertDir, hostPort)
}
func setupCertificates(dir string, tlsc *tls.Config) error {
logrus.Debugf("Looking for TLS certificates and private keys in %s", dir)
fs, err := ioutil.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil
}
if os.IsPermission(err) {
logrus.Debugf("Skipping scan of %s due to permission error: %v", dir, err)
return nil
}
return err
}
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 {
return err
}
tlsc.RootCAs.AppendCertsFromPEM(data)
}
if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name()
keyName := certName[:len(certName)-5] + ".key"
logrus.Debugf(" cert: %s", fullPath)
if !hasFile(fs, keyName) {
return errors.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName)
}
cert, err := tls.LoadX509KeyPair(filepath.Join(dir, certName), filepath.Join(dir, keyName))
if err != nil {
return err
}
tlsc.Certificates = append(tlsc.Certificates, cert)
}
if strings.HasSuffix(f.Name(), ".key") {
keyName := f.Name()
certName := keyName[:len(keyName)-4] + ".cert"
logrus.Debugf(" key: %s", fullPath)
if !hasFile(fs, certName) {
return errors.Errorf("missing client certificate %s for key %s", certName, keyName)
}
}
}
return nil
}
func hasFile(files []os.FileInfo, name string) bool {
for _, f := range files {
if f.Name() == name {
return true
}
}
return false
}
// 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) {

View File

@ -12,26 +12,26 @@ import (
"github.com/pkg/errors"
)
// Image is a Docker-specific implementation of types.Image with a few extra methods
// Image is a Docker-specific implementation of types.ImageCloser with a few extra methods
// which are specific to Docker.
type Image struct {
types.Image
types.ImageCloser
src *dockerImageSource
}
// 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.Image, error) {
func newImage(ctx *types.SystemContext, ref dockerReference) (types.ImageCloser, error) {
s, err := newImageSource(ctx, ref)
if err != nil {
return nil, err
}
img, err := image.FromSource(s)
img, err := image.FromSource(ctx, s)
if err != nil {
return nil, err
}
return &Image{Image: img, src: s}, nil
return &Image{ImageCloser: img, src: s}, nil
}
// SourceRefFullName returns a fully expanded name for the repository this image is in.

View File

@ -236,7 +236,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
if !successStatus(res.StatusCode) {
err = errors.Wrapf(client.HandleErrorResponse(res), "Error uploading manifest to %s", path)
if isManifestInvalidError(errors.Cause(err)) {
err = types.ManifestTypeRejectedError{Err: err}
@ -246,6 +246,12 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
return nil
}
// successStatus returns true if the argument is a successful HTTP response
// code (in the range 200 - 399 inclusive).
func successStatus(status int) bool {
return status >= 200 && status <= 399
}
// isManifestInvalidError returns true iff err from client.HandleErrorReponse is a “manifest invalid” error.
func isManifestInvalidError(err error) bool {
errors, ok := err.(errcode.Errors)

View File

@ -52,6 +52,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() []types.BlobInfo {
return 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 {
@ -67,7 +72,12 @@ func simplifyContentType(contentType string) string {
// 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 *dockerImageSource) GetManifest() ([]byte, string, error) {
// 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) {
if instanceDigest != nil {
return s.fetchManifest(context.TODO(), instanceDigest.String())
}
err := s.ensureManifestIsLoaded(context.TODO())
if err != nil {
return nil, "", err
@ -94,18 +104,12 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
}
// GetTargetManifest returns an image's manifest given a digest.
// This is mainly used to retrieve a single image's manifest out of a manifest list.
func (s *dockerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return s.fetchManifest(context.TODO(), digest.String())
}
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
//
// 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
// and used by GetSignatures are consistent, otherwise we would get spurious
// we need to ensure that the digest of the manifest returned by GetManifest(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 {
if s.cachedManifest != nil {
@ -176,22 +180,30 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
return res.Body, getBlobSize(res), nil
}
func (s *dockerImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (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) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if err := s.c.detectProperties(ctx); err != nil {
return nil, err
}
switch {
case s.c.signatureBase != nil:
return s.getSignaturesFromLookaside(ctx)
return s.getSignaturesFromLookaside(ctx, instanceDigest)
case s.c.supportsSignatures:
return s.getSignaturesFromAPIExtension(ctx)
return s.getSignaturesFromAPIExtension(ctx, instanceDigest)
default:
return [][]byte{}, nil
}
}
// manifestDigest returns a digest of the manifest, either from the supplied reference or from a fetched manifest.
func (s *dockerImageSource) manifestDigest(ctx context.Context) (digest.Digest, error) {
// manifestDigest returns a digest of the manifest, from instanceDigest if non-nil; or from the supplied reference,
// or finally, from a fetched manifest.
func (s *dockerImageSource) manifestDigest(ctx context.Context, instanceDigest *digest.Digest) (digest.Digest, error) {
if instanceDigest != nil {
return *instanceDigest, nil
}
if digested, ok := s.ref.ref.(reference.Digested); ok {
d := digested.Digest()
if d.Algorithm() == digest.Canonical {
@ -206,8 +218,8 @@ func (s *dockerImageSource) manifestDigest(ctx context.Context) (digest.Digest,
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
// which is not nil.
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context) ([][]byte, error) {
manifestDigest, err := s.manifestDigest(ctx)
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
if err != nil {
return nil, err
}
@ -276,8 +288,8 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (
}
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context) ([][]byte, error) {
manifestDigest, err := s.manifestDigest(ctx)
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
if err != nil {
return nil, err
}

View File

@ -122,11 +122,12 @@ func (ref dockerReference) PolicyConfigurationNamespaces() []string {
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
}
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned Image.
// 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.
func (ref dockerReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// 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)
}

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/containers/image/docker/reference"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/opencontainers/go-digest"
@ -18,8 +19,6 @@ 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
@ -107,7 +106,7 @@ func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types
if inputInfo.Size == -1 { // Ouch, we need to stream the blob into a temporary file just to determine the size.
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
}
@ -168,7 +167,7 @@ func (d *Destination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
func (d *Destination) PutManifest(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")
}
@ -177,12 +176,12 @@ func (d *Destination) PutManifest(m []byte) error {
}
layerPaths := []string{}
for _, l := range man.Layers {
for _, l := range man.LayersDescriptors {
layerPaths = append(layerPaths, l.Digest.String())
}
items := []ManifestItem{{
Config: man.Config.Digest.String(),
Config: man.ConfigDescriptor.Digest.String(),
RepoTags: []string{d.repoTag},
Layers: layerPaths,
Parent: "",

View File

@ -24,8 +24,8 @@ type Source struct {
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.
}
@ -156,7 +156,7 @@ func (s *Source) ensureCachedDataIsPresent() error {
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)
}
@ -194,12 +194,12 @@ 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 {
@ -249,28 +249,34 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image
// 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 *Source) GetManifest() ([]byte, string, error) {
// 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) {
if instanceDigest != nil {
// How did we even get here? GetManifest(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,
})
@ -284,13 +290,6 @@ func (s *Source) GetManifest() ([]byte, string, error) {
return s.generatedManifest, manifest.DockerV2Schema2MediaType, nil
}
// GetTargetManifest returns an image's manifest given a digest. This is mainly used to retrieve a single image's manifest
// out of a manifest list.
func (s *Source) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
// How did we even get here? GetManifest() above has returned a manifest.DockerV2Schema2MediaType.
return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
}
type readCloseWrapper struct {
io.Reader
closeFunc func() error
@ -313,7 +312,7 @@ 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,
if li, ok := s.knownLayers[info.Digest]; ok { // diffID is a digest of the uncompressed tarball,
stream, err := s.openTarComponent(li.path)
if err != nil {
return nil, 0, err
@ -355,6 +354,13 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
}
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
func (s *Source) GetSignatures(ctx context.Context) ([][]byte, error) {
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (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) 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.
return nil, errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
}
return [][]byte{}, nil
}

View File

@ -1,6 +1,9 @@
package tarfile
import "github.com/opencontainers/go-digest"
import (
"github.com/containers/image/manifest"
"github.com/opencontainers/go-digest"
)
// Various data structures.
@ -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"`
}

View File

@ -2,6 +2,7 @@ package image
import (
"encoding/json"
"fmt"
"runtime"
"github.com/containers/image/manifest"
@ -21,7 +22,7 @@ type platformSpec struct {
// A manifestDescriptor references a platform-specific manifest.
type manifestDescriptor struct {
descriptor
manifest.Schema2Descriptor
Platform platformSpec `json:"platform"`
}
@ -31,22 +32,36 @@ type manifestList struct {
Manifests []manifestDescriptor `json:"manifests"`
}
func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (genericManifest, error) {
list := manifestList{}
if err := json.Unmarshal(manblob, &list); err != nil {
return nil, err
// 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) {
wantedArch := runtime.GOARCH
if ctx != nil && ctx.ArchitectureChoice != "" {
wantedArch = ctx.ArchitectureChoice
}
wantedOS := runtime.GOOS
if ctx != nil && ctx.OSChoice != "" {
wantedOS = ctx.OSChoice
}
list := manifestList{}
if err := json.Unmarshal(blob, &list); err != nil {
return "", err
}
var targetManifestDigest digest.Digest
for _, d := range list.Manifests {
if d.Platform.Architecture == runtime.GOARCH && d.Platform.OS == runtime.GOOS {
targetManifestDigest = d.Digest
break
if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
return d.Digest, nil
}
}
if targetManifestDigest == "" {
return nil, errors.New("no supported platform found in manifest list")
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)
if err != nil {
return nil, err
}
manblob, mt, err := src.GetTargetManifest(targetManifestDigest)
manblob, mt, err := src.GetManifest(&targetManifestDigest)
if err != nil {
return nil, err
}
@ -59,5 +74,20 @@ func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (gen
return nil, errors.Errorf("Manifest image does not match selected manifest digest %s", targetManifestDigest)
}
return manifestInstanceFromBlob(src, manblob, mt)
return manifestInstanceFromBlob(ctx, 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) {
// For now this only handles manifest.DockerV2ListMediaType; we can generalize it later,
// probably along with manifest list editing.
blob, mt, err := src.Manifest()
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)
}

View File

@ -2,9 +2,6 @@ package image
import (
"encoding/json"
"regexp"
"strings"
"time"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
@ -14,87 +11,25 @@ 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 {
return &manifestSchema1{m: manifest.Schema1FromComponents(ref, fsLayers, history, architecture)}
}
func (m *manifestSchema1) serialize() ([]byte, error) {
return m.m.Serialize()
}
func (m *manifestSchema1) manifestMIMEType() string {
@ -104,7 +39,7 @@ 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.
@ -128,11 +63,7 @@ func (m *manifestSchema1) 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 *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 m.m.LayerInfos()
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
@ -153,22 +84,11 @@ 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
}
return &types.ImageInspectInfo{
Tag: m.Tag,
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
}, nil
return m.m.Inspect(nil)
}
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
@ -181,25 +101,18 @@ func (m *manifestSchema1) 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 *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
copy := *m
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 = ""
}
}
@ -209,7 +122,21 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
// We have 2 MIME types for schema 1, which are basically equivalent (even the un-"Signed" MIME type will be rejected if there isnt a signature; so,
// handle conversions between them by doing nothing.
case manifest.DockerV2Schema2MediaType:
return copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
m2, err := copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
if err != nil {
return nil, err
}
return memoryImageFromManifest(m2), nil
case imgspecv1.MediaTypeImageManifest:
// We can't directly convert to OCI, but we can transitively convert via a Docker V2.2 Distribution manifest
m2, err := copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
if err != nil {
return nil, err
}
return m2.UpdatedImage(types.ManifestUpdateOptions{
ManifestMIMEType: imgspecv1.MediaTypeImageManifest,
InformationOnly: options.InformationOnly,
})
default:
return nil, errors.Errorf("Conversion of image manifest from %s to %s is not implemented", manifest.DockerV2Schema1SignedMediaType, options.ManifestMIMEType)
}
@ -217,102 +144,32 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
return memoryImageFromManifest(&copy), 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) (types.Image, error) {
if len(m.History) == 0 {
func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.BlobInfo, layerDiffIDs []digest.Digest) (genericManifest, error) {
if len(m.m.History) == 0 {
// What would this even mean?! Anyhow, the rest of the code depends on fsLayers[0] and history[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.History) != len(m.m.FSLayers) {
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.m.History), 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.History) - 1; v1Index >= 0; v1Index-- {
v2Index := (len(m.m.History) - 1) - v1Index
var v1compat v1Compatibility
if err := json.Unmarshal([]byte(m.History[v1Index].V1Compatibility), &v1compat); err != nil {
var v1compat manifest.Schema1V1Compatibility
if err := json.Unmarshal([]byte(m.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 {
var size int64
if uploadedLayerInfos != nil {
@ -322,54 +179,23 @@ 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.ToSchema2(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),
}
m2 := manifestSchema2FromComponents(configDescriptor, nil, configJSON, layers)
return memoryImageFromManifest(m2), 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)
return manifestSchema2FromComponents(configDescriptor, nil, configJSON, layers), nil
}

View File

@ -29,54 +29,44 @@ var gzippedEmptyLayer = []byte{
// 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"`
}
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
@ -105,9 +95,9 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
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,
Digest: m.m.ConfigDescriptor.Digest,
Size: m.m.ConfigDescriptor.Size,
URLs: m.m.ConfigDescriptor.URLs,
})
if err != nil {
return nil, err
@ -118,8 +108,8 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
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 +120,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 m.m.LayerInfos()
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
@ -149,21 +131,18 @@ func (m *manifestSchema2) EmbeddedDockerReferenceConflicts(ref reference.Named)
}
func (m *manifestSchema2) imageInspectInfo() (*types.ImageInspectInfo, error) {
config, err := m.ConfigBlob()
if err != nil {
return nil, err
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()
if err != nil {
return nil, err
}
return config, nil
}
v1 := &v1Image{}
if err := json.Unmarshal(config, v1); err != nil {
return nil, err
}
return &types.ImageInspectInfo{
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
}, nil
return m.m.Inspect(getter)
}
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
@ -176,17 +155,14 @@ 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.
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.
@ -204,6 +180,15 @@ func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (typ
return memoryImageFromManifest(&copy), nil
}
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() (types.Image, error) {
configOCI, err := m.OCIConfig()
if err != nil {
@ -214,18 +199,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
@ -244,14 +227,14 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
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 := ""
@ -279,10 +262,10 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
}
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++
}
@ -293,7 +276,7 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
}
v1ID = v
fakeImage := v1Compatibility{
fakeImage := manifest.Schema1V1Compatibility{
ID: v1ID,
Parent: parentV1ID,
Comment: historyEntry.Comment,
@ -307,8 +290,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
}

View File

@ -1,57 +1,14 @@
package image
import (
"time"
"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.
@ -87,43 +44,24 @@ type genericManifest interface {
UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error)
}
func manifestInstanceFromBlob(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":
// 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 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(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, 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))
for i, layer := range layers {
info.Layers[i] = layer.Digest.String()
}
return info, nil
return m.imageInspectInfo()
}

View File

@ -33,11 +33,6 @@ func (i *memoryImage) Reference() types.ImageReference {
return nil
}
// Close removes resources associated with an initialized UnparsedImage, if any.
func (i *memoryImage) Close() error {
return nil
}
// Size returns the size of the image as stored, if known, or -1 if not.
func (i *memoryImage) Size() (int64, error) {
return -1, nil
@ -67,7 +62,9 @@ func (i *memoryImage) Inspect() (*types.ImageInspectInfo, error) {
return inspectManifest(i.genericManifest)
}
// IsMultiImage returns true if the image's manifest is a list of images, false otherwise.
func (i *memoryImage) IsMultiImage() bool {
return false
// 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() []types.BlobInfo {
return nil
}

View File

@ -12,41 +12,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,7 +49,7 @@ 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.
@ -67,9 +60,9 @@ func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
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,
Digest: m.m.Config.Digest,
Size: m.m.Config.Size,
URLs: m.m.Config.URLs,
})
if err != nil {
return nil, err
@ -80,8 +73,8 @@ func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
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
}
@ -107,11 +100,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})
}
return blobs
return m.m.LayerInfos()
}
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
@ -122,21 +111,18 @@ func (m *manifestOCI1) EmbeddedDockerReferenceConflicts(ref reference.Named) boo
}
func (m *manifestOCI1) imageInspectInfo() (*types.ImageInspectInfo, error) {
config, err := m.ConfigBlob()
if err != nil {
return nil, err
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()
if err != nil {
return nil, err
}
return config, nil
}
v1 := &v1Image{}
if err := json.Unmarshal(config, v1); err != nil {
return nil, err
}
return &types.ImageInspectInfo{
DockerVersion: v1.DockerVersion,
Created: v1.Created,
Labels: v1.Config.Labels,
Architecture: v1.Architecture,
Os: v1.OS,
}, nil
return m.m.Inspect(getter)
}
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
@ -149,18 +135,14 @@ 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.
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.
@ -176,17 +158,26 @@ func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.
return memoryImageFromManifest(&copy), 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
}

View File

@ -4,12 +4,22 @@
package image
import (
"github.com/containers/image/manifest"
"github.com/containers/image/types"
)
// FromSource returns a types.Image implementation for source.
// The caller must call .Close() on the returned Image.
// imageCloser implements types.ImageCloser, perhaps allowing simple users
// to use a single object without having keep a reference to a types.ImageSource
// only to call types.ImageSource.Close().
type imageCloser struct {
types.Image
src types.ImageSource
}
// FromSource returns a types.ImageCloser implementation for the default instance of source.
// If source is a manifest list, .Manifest() still returns the manifest list,
// but other methods transparently return data from an appropriate image instance.
//
// The caller must call .Close() on the returned ImageCloser.
//
// FromSource “takes ownership” of the input ImageSource and will call src.Close()
// when the image is closed. (This does not prevent callers from using both the
@ -18,8 +28,19 @@ import (
//
// 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(src types.ImageSource) (types.Image, error) {
return FromUnparsedImage(UnparsedFromSource(src))
func FromSource(ctx *types.SystemContext, src types.ImageSource) (types.ImageCloser, error) {
img, err := FromUnparsedImage(ctx, UnparsedInstance(src, nil))
if err != nil {
return nil, err
}
return &imageCloser{
Image: img,
src: src,
}, nil
}
func (ic *imageCloser) Close() error {
return ic.src.Close()
}
// sourcedImage is a general set of utilities for working with container images,
@ -38,27 +59,22 @@ type sourcedImage struct {
}
// FromUnparsedImage returns a types.Image implementation for unparsed.
// The caller must call .Close() on the returned Image.
// If unparsed represents a manifest list, .Manifest() still returns the manifest list,
// but other methods transparently return data from an appropriate single image.
//
// FromSource “takes ownership” of the input UnparsedImage and will call uparsed.Close()
// when the image is closed. (This does not prevent callers from using both the
// UnparsedImage and ImageSource objects simultaneously, but it means that they only need to
// keep a reference to the Image.)
func FromUnparsedImage(unparsed *UnparsedImage) (types.Image, error) {
// The Image must not be used after the underlying ImageSource is Close()d.
func FromUnparsedImage(ctx *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.
// Also, we do not explicitly implement types.Image.Close; we let the implementation fall through to
// unparsed.Close.
// 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()
if err != nil {
return nil, err
}
parsedManifest, err := manifestInstanceFromBlob(unparsed.src, manifestBlob, manifestMIMEType)
parsedManifest, err := manifestInstanceFromBlob(ctx, unparsed.src, manifestBlob, manifestMIMEType)
if err != nil {
return nil, err
}
@ -85,6 +101,6 @@ func (i *sourcedImage) Inspect() (*types.ImageInspectInfo, error) {
return inspectManifest(i.genericManifest)
}
func (i *sourcedImage) IsMultiImage() bool {
return i.manifestMIMEType == manifest.DockerV2ListMediaType
func (i *sourcedImage) LayerInfosForCopy() []types.BlobInfo {
return i.UnparsedImage.LayerInfosForCopy()
}

View File

@ -11,8 +11,10 @@ import (
)
// UnparsedImage implements types.UnparsedImage .
// An UnparsedImage is a pair of (ImageSource, instance digest); it can represent either a manifest list or a single image instance.
type UnparsedImage struct {
src types.ImageSource
instanceDigest *digest.Digest
cachedManifest []byte // A private cache for Manifest(); nil if not yet known.
// A private cache for Manifest(), may be the empty string if guessing failed.
// Valid iff cachedManifest is not nil.
@ -20,49 +22,41 @@ type UnparsedImage struct {
cachedSignatures [][]byte // A private cache for Signatures(); nil if not yet known.
}
// UnparsedFromSource returns a types.UnparsedImage implementation for source.
// The caller must call .Close() on the returned UnparsedImage.
// UnparsedInstance returns a types.UnparsedImage implementation for (source, instanceDigest).
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list).
//
// UnparsedFromSource “takes ownership” of the input ImageSource and will call src.Close()
// when the image is closed. (This does not prevent callers from using both the
// UnparsedImage and ImageSource objects simultaneously, but it means that they only need to
// keep a reference to the UnparsedImage.)
func UnparsedFromSource(src types.ImageSource) *UnparsedImage {
return &UnparsedImage{src: src}
// The UnparsedImage must not be used after the underlying ImageSource is Close()d.
func UnparsedInstance(src types.ImageSource, instanceDigest *digest.Digest) *UnparsedImage {
return &UnparsedImage{
src: src,
instanceDigest: instanceDigest,
}
}
// Reference returns the reference used to set up this source, _as specified by the user_
// (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image.
func (i *UnparsedImage) Reference() types.ImageReference {
// Note that this does not depend on instanceDigest; e.g. all instances within a manifest list need to be signed with the manifest list identity.
return i.src.Reference()
}
// Close removes resources associated with an initialized UnparsedImage, if any.
func (i *UnparsedImage) Close() error {
return i.src.Close()
}
// 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) {
if i.cachedManifest == nil {
m, mt, err := i.src.GetManifest()
m, mt, err := i.src.GetManifest(i.instanceDigest)
if err != nil {
return nil, "", err
}
// ImageSource.GetManifest does not do digest verification, but we do;
// this immediately protects also any user of types.Image.
ref := i.Reference().DockerReference()
if ref != nil {
if canonical, ok := ref.(reference.Canonical); ok {
digest := digest.Digest(canonical.Digest())
matches, err := manifest.MatchesDigest(m, digest)
if err != nil {
return nil, "", errors.Wrap(err, "Error computing manifest digest")
}
if !matches {
return nil, "", errors.Errorf("Manifest does not match provided manifest digest %s", digest)
}
if digest, haveDigest := i.expectedManifestDigest(); haveDigest {
matches, err := manifest.MatchesDigest(m, digest)
if err != nil {
return nil, "", errors.Wrap(err, "Error computing manifest digest")
}
if !matches {
return nil, "", errors.Errorf("Manifest does not match provided manifest digest %s", digest)
}
}
@ -72,10 +66,26 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) {
return i.cachedManifest, i.cachedManifestMIMEType, nil
}
// expectedManifestDigest returns a the expected value of the manifest digest, and an indicator whether it is known.
// The bool return value seems redundant with digest != ""; it is used explicitly
// to refuse (unexpected) situations when the digest exists but is "".
func (i *UnparsedImage) expectedManifestDigest() (digest.Digest, bool) {
if i.instanceDigest != nil {
return *i.instanceDigest, true
}
ref := i.Reference().DockerReference()
if ref != nil {
if canonical, ok := ref.(reference.Canonical); ok {
return canonical.Digest(), true
}
}
return "", false
}
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
func (i *UnparsedImage) Signatures(ctx context.Context) ([][]byte, error) {
if i.cachedSignatures == nil {
sigs, err := i.src.GetSignatures(ctx)
sigs, err := i.src.GetSignatures(ctx, i.instanceDigest)
if err != nil {
return nil, err
}
@ -83,3 +93,10 @@ func (i *UnparsedImage) Signatures(ctx context.Context) ([][]byte, error) {
}
return i.cachedSignatures, nil
}
// 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 *UnparsedImage) LayerInfosForCopy() []types.BlobInfo {
return i.src.LayerInfosForCopy()
}

Some files were not shown because too many files have changed in this diff Show More