Compare commits
76 Commits
release-1.
...
master
Author | SHA1 | Date |
---|---|---|
Antonio Murdaca | 54e76afc03 | |
Daniel J Walsh | 4fee97abe3 | |
Kei Sawada | a50f352eb4 | |
Mrunal Patel | ed40d645cd | |
W. Trevor King | 8dbc2d1fff | |
Mrunal Patel | ddb14b7303 | |
Mrunal Patel | 924821e4bf | |
W. Trevor King | 080b84dfcd | |
Mrunal Patel | 214096b7ed | |
Antonio Murdaca | 8c87b6104f | |
Mrunal Patel | b7995aa526 | |
W. Trevor King | 822a6516cf | |
W. Trevor King | 523326b7ba | |
W. Trevor King | 826298483a | |
Mrunal Patel | 7851115693 | |
Antonio Murdaca | 77561e95cf | |
Antonio Murdaca | cbfdda868a | |
W. Trevor King | 282b900433 | |
Mrunal Patel | 1bb5846d7d | |
W. Trevor King | 15d839ea0d | |
W. Trevor King | bf8a99c085 | |
W. Trevor King | 2bf750c871 | |
Mrunal Patel | ba2b4a03d0 | |
W. Trevor King | 8c7c70c2db | |
W. Trevor King | e124834b0d | |
Antonio Murdaca | cb8033cd19 | |
Antonio Murdaca | 8c190a683c | |
Mrunal Patel | d0e0303921 | |
Antonio Murdaca | 8d2a572ead | |
Daniel J Walsh | 22e25158ca | |
Vincent Batts | 27c2eda635 | |
Daniel J Walsh | 23d20c9db5 | |
Mrunal Patel | 41aaf4e3d8 | |
Giuseppe Scrivano | 2cb22eba49 | |
Giuseppe Scrivano | 6bb1b7e17d | |
Giuseppe Scrivano | b1b380d67b | |
Antonio Murdaca | 6f4d7c1ae0 | |
Mrunal Patel | c351bc81e1 | |
Giuseppe Scrivano | b5167d4e8f | |
Giuseppe Scrivano | 1f75ec82e1 | |
Giuseppe Scrivano | 3881f375b9 | |
Mrunal Patel | ad46c581fa | |
Daniel J Walsh | 3c1c6d047e | |
Mrunal Patel | a34038350c | |
Aaron Crickenberger | a28eb8374e | |
Mrunal Patel | 295a11eb17 | |
Mrunal Patel | 28976738de | |
Haoran Wang | 88b13dfddf | |
Jianyong Wu | 8b1fefad71 | |
Mrunal Patel | 6b91df3da7 | |
Antonio Murdaca | de0be63495 | |
Daniel J Walsh | a85f3127d8 | |
Daniel J Walsh | 6c0b79b706 | |
Mrunal Patel | aee7dea272 | |
Antonio Murdaca | e344ad105a | |
Antonio Murdaca | 43119a7b13 | |
Antonio Murdaca | ecc572e7cf | |
Antonio Murdaca | 455245e65b | |
Antonio Murdaca | 7d2bde110a | |
Nalin Dahyabhai | fa90249c59 | |
Nalin Dahyabhai | 72442d0957 | |
Nalin Dahyabhai | 0ab8c507f4 | |
Nalin Dahyabhai | 492f758176 | |
Nalin Dahyabhai | 893aa4e8c7 | |
Nalin Dahyabhai | 6a456d1502 | |
Nalin Dahyabhai | 5ea050fc12 | |
Nalin Dahyabhai | ff7bbb4f0d | |
Nalin Dahyabhai | f3b7065bd8 | |
Nalin Dahyabhai | 553979e1fc | |
Nalin Dahyabhai | 0651d3a8de | |
Mrunal Patel | 2fa1f3f74a | |
Antonio Murdaca | d91df68638 | |
Mrunal Patel | da50e6ca11 | |
Mrunal Patel | ebc249cad8 | |
Antonio Murdaca | f317ffce5b | |
Antonio Murdaca | 06904d4dbb |
|
@ -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>
|
|
@ -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" \
|
||||
|
|
15
Makefile
15
Makefile
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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"]
|
|
@ -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
|
|
@ -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 :
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
d ${RUN_DIRECTORY}/crio - - - - -
|
||||
d /etc/crio - - - - -
|
||||
Z /etc/crio - - - - -
|
||||
d ${STATE_DIRECTORY}/origin - - - - -
|
||||
d ${STATE_DIRECTORY}/kubelet - - - - -
|
|
@ -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"]
|
|
@ -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
|
||||
```
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
d ${RUN_DIRECTORY}/crio - - - - -
|
||||
d /etc/crio - - - - -
|
||||
Z /etc/crio - - - - -
|
||||
d ${STATE_DIRECTORY}/origin - - - - -
|
||||
d ${STATE_DIRECTORY}/kubelet - - - - -
|
|
@ -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"
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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)
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
d ${RUN_DIRECTORY}/crio - - - - -
|
||||
d /etc/crio - - - - -
|
||||
Z /etc/crio - - - - -
|
||||
d ${STATE_DIRECTORY}/origin - - - - -
|
||||
d ${STATE_DIRECTORY}/kubelet - - - - -
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 }}"
|
||||
|
|
BIN
docs/play.png
BIN
docs/play.png
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
|
@ -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
|
|
@ -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
|
5
hooks.md
5
hooks.md
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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, ©.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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
105
test/image.bats
105
test/image.bats
|
@ -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" != "" ]
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 ]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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": {
|
||||
|
|
28
tutorial.md
28
tutorial.md
|
@ -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:
|
||||
|
|
|
@ -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"`
|
||||
|
|
13
vendor.conf
13
vendor.conf
|
@ -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
|
||||
|
|
|
@ -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 let’s 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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 blob’s 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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 transport’s
|
||||
// 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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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: "",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 isn’t 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(©), 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
|
||||
}
|
||||
|
|
|
@ -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(©), 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(©), 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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue