Compare commits

...

62 Commits
v0.5 ... v0.10

Author SHA1 Message Date
Daniel J Walsh
123493895f Merge pull request #370 from rhatdan/master
Bump to version 0.10
2017-12-26 07:18:41 -05:00
Daniel J Walsh
129fb109d5 Bump to version 0.10
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2017-12-24 06:59:23 -05:00
Boaz Shuster
979c945674 Display Config and Manifest as strings
Builder has the Config and the Manifest fields which are []byte.
The type is []byte because it's eaiser to serialize them that way.
However, when these fields are displayed in the following way:

buildah inspect -f '{{.Config}} {{.Manifest}}' IMAGE

they might be shown as a byte slice.

This patch suggests to use a struct that wraps Builder's exposed
fields such as Config, Manifest and Container as strings and
thus make them readable when inspecting specific field.

Signed-off-by: Boaz Shuster <ripcurld.github@gmail.com>

Closes: #368
Approved by: rhatdan
2017-12-19 22:16:37 +00:00
TomSweeneyRedHat
c77a8d39f1 Create Registry in Docker for Travis CI
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #290
Approved by: rhatdan
2017-12-18 21:26:57 +00:00
TomSweeneyRedHat
f4151372e5 Create Tutorials menu and add link to README.md
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #367
Approved by: rhatdan
2017-12-15 21:28:32 +00:00
Nalin Dahyabhai
0705787a07 Bump containers/image
Bump containers/image to 4fdf9c9b8a1e014705581ff77df6446e67a8318d.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #366
Approved by: nalind
2017-12-15 17:54:40 +00:00
Nalin Dahyabhai
a5129ec3eb Add a test that 'rmi' works with truncated image IDs
Add a test to ensure that 'buildah rmi' works if passed a truncated
version of an image's ID.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #361
Approved by: rhatdan
2017-12-15 12:13:47 +00:00
Nalin Dahyabhai
47ac96155f Use configured registries to resolve image names
When locating an image for pulling, inspection, or pushing, if we're
given an image name that doesn't include a domain/registry, try building
a set of candidate names using the configured registries as domains, and
then pull/inspect/push using the first of those names that works.

If a name that we're given corresponds to a prefix of the ID of a local
image, skip completion and use the ID directly instead.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #360
Approved by: rhatdan
2017-12-14 22:21:16 +00:00
Nalin Dahyabhai
8b2b56d9b8 Update to work with newer image library
Update shallowCopy() to work with the newer version of image.
Remove things from Push() that we don't need to do any more.
Preserve digests in image names, make sure we update creation times, and
add a test to ensure that we can pull, commit, and push using such names
as sources.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #187
Approved by: rhatdan
2017-12-14 20:57:13 +00:00
Nalin Dahyabhai
544e63de42 Bump containers/image and containers/storage
Update containers/image and containers/storage to current master
(17449738f2bb4c6375c20dcdcfe2a6cccf03f312 and
0d32dfce498e06c132c60dac945081bf44c22464, respectively).

Also updates github.com/docker/docker, golang.org/x/sys, and
golang.org/x/text.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #187
Approved by: rhatdan
2017-12-14 20:57:13 +00:00
Máirín Duffy
43a025ebf9 updating logo reference in README
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Removing old 'underwear hat' logo, replacing with updated logo artwork

Closes: #359
Approved by: rhatdan
2017-12-13 17:02:17 +00:00
Antoine Beaupré
6116d6a9bc install golang metapackage from backports
`golang-1.8` *is* available in stretch, but /usr/bin/go does *not*
point to it by default, *unless* the `golang` meta-package is
installed from backports.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>

Closes: #354
Approved by: rhatdan
2017-12-13 12:38:52 +00:00
Antoine Beaupré
e5aa6c9fc5 add install.runc target
We need this for platforms like Ubuntu and Debian that do not ship a
standard (post 1.0rc4) version of runc.

I'm assuming here this is why we're building `runc` on our own here -
but it doesn't make sense to just do that while leaving only a symlink
in $PWD. We want to actually install the thing as well. So we add an
`install.runc` target, similar to `install.libseccomp.sudo` to make
sure we install `runc` in the right location.

An alternative to this would be to change the documentation to do the
`install` command by hand, but this is more error-prone. As runc
trickles down to the Debian distros, we can then just remove the `make
install.runc` call and, eventually, the target itself.

Closes: #355

Signed-off-by: Antoine Beaupré <anarcat@debian.org>

Closes: #354
Approved by: rhatdan
2017-12-13 12:38:52 +00:00
Antoine Beaupré
95ca6c1e1f run make install as sudo
Explicitly using `sudo` in the `make install` line makes it clear that
the rest of the commands can (and probably should) be ran as non-root.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>

Closes: #354
Approved by: rhatdan
2017-12-13 12:38:52 +00:00
Antoine Beaupré
7244ef44fb add Debian stable install instructions
Note that the instructions may seem unusual to people used to
`apt-key`, but they conform to the [emerging standard](https://wiki.debian.org/DebianRepository/UseThirdParty) for
third-party repositories in Debian.

We use ostree from backports because it matches the version in the
Ubuntu Flatpak PPA. We also explicitly require golang 1.8, which gives
us a 1.8.1 runtime in stretch. We otherwise use the Project Atomic,
but that's only because of Skopeo and similar tools.

Signed-off-by: Antoine Beaupré <anarcat@debian.org>

Closes: #354
Approved by: rhatdan
2017-12-13 12:38:52 +00:00
Antoine Beaupré
9df6f62a4c add headings for different OSes
Signed-off-by: Antoine Beaupré <anarcat@debian.org>

Closes: #354
Approved by: rhatdan
2017-12-13 12:38:52 +00:00
Daniel J Walsh
bf01a80b2b Merge pull request #349 from TomSweeneyRedHat/dev/tsweeney/baseline2
Touchup baseline and rpm tests
2017-12-11 12:00:57 -06:00
Daniel J Walsh
ccd3b3fedb Merge pull request #351 from ripcurld0/small_nitpick
Small nitpick at matches(Since,Before)Image in cmd/buildah/images
2017-12-08 20:49:49 -06:00
Nalin Dahyabhai
4b4e25868c Merge pull request #350 from ipbabble/tutorial2
Add a new tutorial for using Buildah with registries
2017-12-07 17:12:36 -05:00
William Henry
4d943752fe Fixed some more Tom nits.
Signed-off-by: William Henry <whenry@redhat.com>
2017-12-07 10:57:14 -07:00
William Henry
9128a40ada Fixed some Tom nits.
Signed-off-by: William Henry <whenry@redhat.com>
2017-12-07 09:18:13 -07:00
William Henry
8910199181 Add a new tutorial for using Buildah with registries
Signed-off-by: William Henry <whenry@redhat.com>
2017-12-07 08:40:03 -07:00
Fabio Bertinatto
1fc5a49958 Add --chown option to add/copy commands
Signed-off-by: Fabio Bertinatto <fbertina@redhat.com>

Closes: #336
Approved by: rhatdan
2017-12-07 13:45:12 +00:00
Boaz Shuster
98f1533731 Small nitpick at matches(Since,Before)Image in cmd/buildah/images
Signed-off-by: Boaz Shuster <ripcurld.github@gmail.com>
2017-12-07 14:09:45 +02:00
TomSweeneyRedHat
aae843123f Touchup baseline and rpm tests
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>
2017-12-06 11:14:09 -05:00
Nalin Dahyabhai
77804bf256 Merge pull request #348 from vbatts/readme
README: better first glance idea
2017-12-05 13:40:28 -05:00
Vincent Batts
7aaa21d70a README: better first glance idea
Fixes #347

Make the project's first-glance easier to digest.

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
2017-12-05 09:09:44 -05:00
TomSweeneyRedHat
ee9b8cde5a Create rpm and baseline test script
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #346
Approved by: rhatdan
2017-12-04 17:01:00 +00:00
TomSweeneyRedHat
04ea079130 Bump version to 0.9
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #345
Approved by: rhatdan
2017-12-02 11:51:00 +00:00
Nalin Dahyabhai
2dd03d6741 tests/rpm.bats: use Fedora 27
Update tests/rpm.bats to use Fedora 27.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #342
Approved by: rhatdan
2017-12-01 13:22:58 +00:00
TomSweeneyRedHat
1680a5f0a0 Fix iterator and a few typos in baseline test
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #335
Approved by: rhatdan
2017-12-01 00:02:53 +00:00
TomSweeneyRedHat
5dd1a5f3c9 Touchup test scripts for some minor nits
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #335
Approved by: rhatdan
2017-12-01 00:02:53 +00:00
TomSweeneyRedHat
15792b227a Allow push to use the image id
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #341
Approved by: nalind
2017-11-30 23:47:13 +00:00
Daniel J Walsh
38d3cddb0c Make sure builtin volumes have the correct label
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #339
Approved by: nalind
2017-11-28 21:44:17 +00:00
Nalin Dahyabhai
a99d5f0798 Bump the GIT_VALIDATION_EPOCH to a newer version
Bump the GIT_VALIDATION_EPOCH in tests/validate/git-validation.sh to a
later commit.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #340
Approved by: rhatdan
2017-11-28 19:39:37 +00:00
Nalin Dahyabhai
53c3e6434d Bump RPM version to 0.8
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #340
Approved by: rhatdan
2017-11-28 19:39:37 +00:00
Daniel J Walsh
bf40000e72 Bump to v0.8 2017-11-22 16:35:41 +00:00
Daniel J Walsh
fb99d85b76 Need to block access to kernel files systems in /proc and /sys
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #333
Approved by: TomSweeneyRedHat
2017-11-22 16:13:50 +00:00
Daniel J Walsh
85476bf093 Buildah bud does not work with SELinux
buildah bud was not setting the mount label on the image
so SELinux in enforcing mode is blocking writing to the image

This patch also fixes a similar problem with the `buildah mount`
command

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #332
Approved by: TomSweeneyRedHat
2017-11-22 15:36:51 +00:00
Urvashi Mohnani
819c227bf2 Mention docker login in documentation for authentication
Since we fall back to reading the credentials from $HOME/.docker/config
set by docker login when kpod login doesn't have the credentials

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #331
Approved by: rhatdan
2017-11-21 18:06:44 +00:00
TomSweeneyRedHat
4b23819189 Touchup test scripts for some minor nits
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #330
Approved by: rhatdan
2017-11-21 15:39:39 +00:00
Daniel J Walsh
b893112a90 Merge pull request #328 from TomSweeneyRedHat/dev/tsweeney/baselinetest
Create baseline test script
2017-11-21 09:41:03 -05:00
TomSweeneyRedHat
9fa477e303 Create baseline test script
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>
2017-11-19 14:27:38 -05:00
Daniel J Walsh
b7e3320fe4 Bump to 0.7
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2017-11-16 22:00:38 +00:00
Daniel J Walsh
58025ee1be Ignore errors when trying to read containers buildah.json
Since containers can be created using other tools then buildah
we can not fail when they don't have buildah config.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #327
Approved by: nalind
2017-11-16 21:12:38 +00:00
Urvashi Mohnani
7a3bc6efd4 Use credentials from kpod login for buildah
buildah push and from now use the credentials stored in ${XDG_RUNTIME_DIR}/containers/auth.json by kpod login
if the auth file path is changed, buildah push and from can get the credentials from the custom auth file
using the --authfile flag
e.g buildah push --authfile /tmp/auths/myauths.json alpine docker://username/image

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #325
Approved by: rhatdan
2017-11-16 18:08:52 +00:00
Daniel J Walsh
de0fb93f3d Bump to 0.6
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2017-11-15 17:47:53 +00:00
Urvashi Mohnani
4419612150 Add manifest type conversion to buildah push
buildah push supports manifest type conversion when pushing using the 'dir' transport
Manifest types include oci, v2s1, and v2s2
e.g buildah push --format v2s2 alpine dir:my-directory

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #321
Approved by: rhatdan
2017-11-15 13:38:28 +00:00
Urvashi Mohnani
5ececfad2c Vendor in latest container/image
Adds support for converting manifest types when using the dir transport

Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>

Closes: #321
Approved by: rhatdan
2017-11-15 13:38:28 +00:00
TomSweeneyRedHat
4f376bbb5e Set option.terminal appropriately in run
Signed-off-by: TomSweeneyRedHat <tsweeney@redhat.com>

Closes: #323
Approved by: rhatdan
2017-11-14 19:28:51 +00:00
Anthony Green
d03123204d Add RHEL build instructions.
Signed-off-by: Anthony Green <green@redhat.com>

Closes: #322
Approved by: rhatdan
2017-11-10 11:36:11 +00:00
Nalin Dahyabhai
0df1c44b12 tests: check $status whenever we use run
Always be sure to check $status after using the run helper.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
75fbb8483e Test that "run" fails with unresolvable names
Add a test that makes sure that "buildah run" fails if it can't resolve
the name of the user for the container.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
52e2737460 Rework how we do UID resolution in images
* Use chroot() instead of trying to read the right file ourselves.

This should resolve #66.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
c83cd3fba9 Accept numeric USER values with no group ID
Change our behavior when we're given USER with a numeric UID and no GID,
so that we no longer error out if the UID doesn't correspond to a known
user so that we can use that user's primary GID.  Instead, use GID 0.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
d41ac23a03 Add a test for USER symlink resolution
Add a test that makes sure we catch cases where we attempt to open a
file in the container's tree that's actually a symlink that points out
of the tree.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
dbebeb7235 Never use host methods for parsing USER values
Drop fallbacks for resolving USER values that attempt to look up names
on the host, since that's never predictable.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
9e129fd653 fopenContainerFile: scope filename lookups better
Switch fopenContainerFile from using Stat/Lstat after opening the file
to using openat() to walk the given path, resolving links to keep them
from escaping the container's root fs.  This should resolve #66.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #313
Approved by: rhatdan
2017-11-10 09:58:08 +00:00
Nalin Dahyabhai
0a44c7f162 "run --hostname test": do less setup
We don't need to mount the container for this test or add files to it,
and switching to a smaller base image that already includes a "hostname"
command means we don't need to run a package installer in the container.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #320
Approved by: nalind
2017-11-09 20:27:58 +00:00
Nalin Dahyabhai
b12735358a "run --hostname test": print $output more
Make it easier to troubleshoot the "run --hostname" test.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #320
Approved by: nalind
2017-11-09 20:27:58 +00:00
Nalin Dahyabhai
318beaa720 integration tests: default to /var/tmp
Default to running integration tests using /var/tmp as scratch space,
since it's more likely to support proper SELinux labeling than /tmp,
which is more likely to be on a tmpfs.

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #320
Approved by: nalind
2017-11-09 20:27:57 +00:00
Nalin Dahyabhai
f7dc659e52 Bump github.com/vbatts/tar-split
Update github.com/vbatts/tar-split to v0.10.2 and pin that version
instead of master, to pick up https://github.com/vbatts/tar-split/pull/42

Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>

Closes: #318
Approved by: rhatdan
2017-11-08 17:36:54 +00:00
214 changed files with 10002 additions and 22236 deletions

View File

@@ -23,6 +23,7 @@ dnf install -y \
libselinux-devel \
libselinux-utils \
make \
openssl \
ostree-devel \
which

View File

@@ -9,6 +9,40 @@ host:
required: true
tests:
# Let's create a self signed certificate and get it in the right places
- hostname
- ip a
- ping -c 3 localhost
- cat /etc/hostname
- mkdir -p /home/travis/auth
- openssl req -newkey rsa:4096 -nodes -sha256 -keyout /home/travis/auth/domain.key -x509 -days 2 -out /home/travis/auth/domain.crt -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"
- cp /home/travis/auth/domain.crt /home/travis/auth/domain.cert
- sudo mkdir -p /etc/docker/certs.d/docker.io/
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/docker.io/ca.crt
- sudo mkdir -p /etc/docker/certs.d/localhost:5000/
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/localhost:5000/ca.crt
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/localhost:5000/domain.crt
# Create the credentials file, then start up the Docker registry
- docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > /home/travis/auth/htpasswd
- docker run -d -p 5000:5000 --name registry -v /home/travis/auth:/home/travis/auth:Z -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/home/travis/auth/htpasswd -e REGISTRY_HTTP_TLS_CERTIFICATE=/home/travis/auth/domain.crt -e REGISTRY_HTTP_TLS_KEY=/home/travis/auth/domain.key registry:2
# Test Docker setup
- docker ps --all
- docker images
- ls -alF /home/travis/auth
- docker pull alpine
- docker login localhost:5000 --username testuser --password testpassword
- docker tag alpine localhost:5000/my-alpine
- docker push localhost:5000/my-alpine
- docker ps --all
- docker images
- docker rmi docker.io/alpine
- docker rmi localhost:5000/my-alpine
- docker pull localhost:5000/my-alpine
- docker ps --all
- docker images
- docker rmi localhost:5000/my-alpine
# mount yum repos to inherit injected mirrors from PAPR
- docker run --net=host --privileged -v /etc/yum.repos.d:/etc/yum.repos.d.host:ro
-v $PWD:/code registry.fedoraproject.org/fedora:26 sh -c

View File

@@ -20,10 +20,46 @@ services:
- docker
before_install:
- sudo add-apt-repository -y ppa:duggan/bats
- sudo apt-get -qq update
- sudo apt-get update
- sudo apt-get -qq install bats btrfs-tools git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libselinux1-dev
- sudo apt-get -qq remove libseccomp2
- sudo apt-get -qq update
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
- mkdir /home/travis/auth
install:
# Let's create a self signed certificate and get it in the right places
- hostname
- ip a
- ping -c 3 localhost
- cat /etc/hostname
- openssl req -newkey rsa:4096 -nodes -sha256 -keyout /home/travis/auth/domain.key -x509 -days 2 -out /home/travis/auth/domain.crt -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=localhost"
- cp /home/travis/auth/domain.crt /home/travis/auth/domain.cert
- sudo mkdir -p /etc/docker/certs.d/docker.io/
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/docker.io/ca.crt
- sudo mkdir -p /etc/docker/certs.d/localhost:5000/
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/localhost:5000/ca.crt
- sudo cp /home/travis/auth/domain.crt /etc/docker/certs.d/localhost:5000/domain.crt
# Create the credentials file, then start up the Docker registry
- docker run --entrypoint htpasswd registry:2 -Bbn testuser testpassword > /home/travis/auth/htpasswd
- docker run -d -p 5000:5000 --name registry -v /home/travis/auth:/home/travis/auth:Z -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/home/travis/auth/htpasswd -e REGISTRY_HTTP_TLS_CERTIFICATE=/home/travis/auth/domain.crt -e REGISTRY_HTTP_TLS_KEY=/home/travis/auth/domain.key registry:2
script:
# Let's do some docker stuff just for verification purposes
- docker ps --all
- docker images
- ls -alF /home/travis/auth
- docker pull alpine
- docker login localhost:5000 --username testuser --password testpassword
- docker tag alpine localhost:5000/my-alpine
- docker push localhost:5000/my-alpine
- docker ps --all
- docker images
- docker rmi docker.io/alpine
- docker rmi localhost:5000/my-alpine
- docker pull localhost:5000/my-alpine
- docker ps --all
- docker images
- docker rmi localhost:5000/my-alpine
# Setting up Docker Registry is complete, let's do Buildah testing!
- make install.tools install.libseccomp.sudo all runc validate TAGS="apparmor seccomp containers_image_ostree_stub"
- go test -c -tags "apparmor seccomp `./btrfs_tag.sh` `./libdm_tag.sh` `./ostree_tag.sh` `./selinux_tag.sh`" ./cmd/buildah
- tmp=`mktemp -d`; mkdir $tmp/root $tmp/runroot; sudo PATH="$PATH" ./buildah.test -test.v -root $tmp/root -runroot $tmp/runroot -storage-driver vfs -signature-policy `pwd`/tests/policy.json

View File

@@ -78,6 +78,10 @@ install:
install.completions:
install -m 644 -D contrib/completions/bash/buildah $(DESTDIR)/${BASHINSTALLDIR}/buildah
.PHONY: install.runc
install.runc:
install -m 755 ../../opencontainers/runc/runc $(DESTDIR)/$(BINDIR)/
.PHONY: test-integration
test-integration:
cd tests; ./test_runner.sh

106
README.md
View File

@@ -1,4 +1,4 @@
![buildah logo](https://cdn.rawgit.com/projectatomic/buildah/master/logos/buildah.svg)
![buildah logo](https://cdn.rawgit.com/projectatomic/buildah/master/logos/buildah-logo_large.png)
# [Buildah](https://www.youtube.com/embed/YVk5NgSiUw8) - a tool which facilitates building OCI container images
================================================================
@@ -19,85 +19,45 @@ The Buildah package provides a command line tool which can be used to
**[Changelog](CHANGELOG.md)**
**Installation notes**
**[Installation notes](install.md)**
Prior to installing Buildah, install the following packages on your linux distro:
* make
* golang (Requires version 1.8.1 or higher.)
* bats
* btrfs-progs-devel
* bzip2
* device-mapper-devel
* git
* go-md2man
* gpgme-devel
* glib2-devel
* libassuan-devel
* ostree-devel
* runc (Requires version 1.0 RC4 or higher.)
* skopeo-containers
**[Tutorials](docs/tutorials/tutorials.md)**
In Fedora, you can use this command:
## runc Requirement
```
dnf -y install \
make \
golang \
bats \
btrfs-progs-devel \
device-mapper-devel \
glib2-devel \
gpgme-devel \
libassuan-devel \
ostree-devel \
git \
bzip2 \
go-md2man \
runc \
skopeo-containers
```
Then to install Buildah on Fedora follow the steps in this example:
```
mkdir ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/projectatomic/buildah ./src/github.com/projectatomic/buildah
cd ./src/github.com/projectatomic/buildah
make
make install
buildah --help
```
In Ubuntu zesty and xenial, you can use this command:
```
apt-get -y install software-properties-common
add-apt-repository -y ppa:alexlarsson/flatpak
add-apt-repository -y ppa:gophers/archive
apt-add-repository -y ppa:projectatomic/ppa
apt-get -y -qq update
apt-get -y install bats btrfs-tools git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libostree-dev libseccomp-dev libselinux1-dev skopeo-containers go-md2man
apt-get -y install golang-1.8
```
Then to install Buildah on Ubuntu follow the steps in this example:
```
mkdir ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/projectatomic/buildah ./src/github.com/projectatomic/buildah
cd ./src/github.com/projectatomic/buildah
PATH=/usr/lib/go-1.8/bin:$PATH make runc all TAGS="apparmor seccomp"
make install
buildah --help
```
Buildah uses `runc` to run commands when `buildah run` is used, or when `buildah build-using-dockerfile`
encounters a `RUN` instruction, so you'll also need to build and install a compatible version of
[runc](https://github.com/opencontainers/runc) for Buildah to call for those cases.
## Example
From [`./examples/lighttpd.sh`](examples/lighttpd.sh):
```bash
cat > lighttpd.sh <<EOF
#!/bin/bash -x
ctr1=`buildah from ${1:-fedora}`
## Get all updates and install our minimal httpd server
buildah run $ctr1 -- dnf update -y
buildah run $ctr1 -- dnf install -y lighttpd
## Include some buildtime annotations
buildah config --annotation "com.example.build.host=$(uname -n)" $ctr1
## Run our server and expose the port
buildah config $ctr1 --cmd "/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf"
buildah config $ctr1 --port 80
## Commit this container to an image name
buildah commit $ctr1 ${2:-$USER/lighttpd}
EOF
chmod +x lighttpd.sh
./lighttpd.sh
```
## Commands
| Command | Description |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |

57
add.go
View File

@@ -12,10 +12,16 @@ import (
"time"
"github.com/containers/storage/pkg/archive"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
//AddAndCopyOptions holds options for add and copy commands.
type AddAndCopyOptions struct {
Chown string
}
// addURL copies the contents of the source URL to the destination. This is
// its own function so that deferred closes happen after we're done pulling
// down each item of potentially many.
@@ -58,8 +64,8 @@ func addURL(destination, srcurl string) error {
// Add copies the contents of the specified sources into the container's root
// filesystem, optionally extracting contents of local files that look like
// non-empty archives.
func (b *Builder) Add(destination string, extract bool, source ...string) error {
mountPoint, err := b.Mount("")
func (b *Builder) Add(destination string, extract bool, options AddAndCopyOptions, source ...string) error {
mountPoint, err := b.Mount(b.MountLabel)
if err != nil {
return err
}
@@ -100,6 +106,11 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if len(source) > 1 && (destfi == nil || !destfi.IsDir()) {
return errors.Errorf("destination %q is not a directory", dest)
}
// Find out which user (and group) the destination should belong to.
user, err := b.user(mountPoint, options)
if err != nil {
return err
}
for _, src := range source {
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
// We assume that source is a file, and we're copying
@@ -118,6 +129,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := addURL(d, src); err != nil {
return err
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
@@ -146,6 +160,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := copyWithTar(gsrc, d); err != nil {
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
if !extract || !archive.IsArchivePath(gsrc) {
@@ -161,6 +178,9 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
if err := copyFileWithTar(gsrc, d); err != nil {
return errors.Wrapf(err, "error copying %q to %q", gsrc, d)
}
if err := setOwner(d, user); err != nil {
return err
}
continue
}
// We're extracting an archive into the destination directory.
@@ -172,3 +192,36 @@ func (b *Builder) Add(destination string, extract bool, source ...string) error
}
return nil
}
// user returns the user (and group) information which the destination should belong to.
func (b *Builder) user(mountPoint string, options AddAndCopyOptions) (specs.User, error) {
if options.Chown != "" {
return getUser(mountPoint, options.Chown)
}
return getUser(mountPoint, b.User())
}
// setOwner sets the uid and gid owners of a given path.
// If path is a directory, recursively changes the owner.
func setOwner(path string, user specs.User) error {
fi, err := os.Stat(path)
if err != nil {
return errors.Wrapf(err, "error reading %q", path)
}
if fi.IsDir() {
err2 := filepath.Walk(path, func(p string, info os.FileInfo, we error) error {
if err3 := os.Lchown(p, int(user.UID), int(user.GID)); err3 != nil {
return errors.Wrapf(err3, "error setting ownership of %q", p)
}
return nil
})
if err2 != nil {
return errors.Wrapf(err2, "error walking dir %q to set ownership", path)
}
return nil
}
if err := os.Lchown(path, int(user.UID), int(user.GID)); err != nil {
return errors.Wrapf(err, "error setting ownership of %q", path)
}
return nil
}

View File

@@ -20,7 +20,7 @@ const (
// identify working containers.
Package = "buildah"
// Version for the Package
Version = "0.5"
Version = "0.10"
// The value we use to identify what type of information, currently a
// serialized Builder structure, we are using as per-container state.
// This should only be changed when we make incompatible changes to
@@ -95,6 +95,46 @@ type Builder struct {
DefaultMountsFilePath string `json:"defaultMountsFilePath,omitempty"`
}
// BuilderInfo are used as objects to display container information
type BuilderInfo struct {
Type string
FromImage string
FromImageID string
Config string
Manifest string
Container string
ContainerID string
MountPoint string
ProcessLabel string
MountLabel string
ImageAnnotations map[string]string
ImageCreatedBy string
OCIv1 v1.Image
Docker docker.V2Image
DefaultMountsFilePath string
}
// GetBuildInfo gets a pointer to a Builder object and returns a BuilderInfo object from it.
// This is used in the inspect command to display Manifest and Config as string and not []byte.
func GetBuildInfo(b *Builder) BuilderInfo {
return BuilderInfo{
Type: b.Type,
FromImage: b.FromImage,
FromImageID: b.FromImageID,
Config: string(b.Config),
Manifest: string(b.Manifest),
Container: b.Container,
ContainerID: b.ContainerID,
MountPoint: b.MountPoint,
ProcessLabel: b.ProcessLabel,
ImageAnnotations: b.ImageAnnotations,
ImageCreatedBy: b.ImageCreatedBy,
OCIv1: b.OCIv1,
Docker: b.Docker,
DefaultMountsFilePath: b.DefaultMountsFilePath,
}
}
// BuilderOptions are used to initialize a new Builder.
type BuilderOptions struct {
// FromImage is the name of the image which should be used as the
@@ -159,6 +199,10 @@ type ImportFromImageOptions struct {
// specified, indicating that the shared, system-wide default policy
// should be used.
SignaturePolicyPath string
// github.com/containers/image/types SystemContext to hold information
// about which registries we should check for completing image names
// that don't include a domain portion.
SystemContext *types.SystemContext
}
// NewBuilder creates a new build container.

View File

@@ -2,10 +2,17 @@ package main
import (
"github.com/pkg/errors"
"github.com/projectatomic/buildah"
"github.com/urfave/cli"
)
var (
addAndCopyFlags = []cli.Flag{
cli.StringFlag{
Name: "chown",
Usage: "Set the user and group ownership of the destination content",
},
}
addDescription = "Adds the contents of a file, URL, or directory to a container's working\n directory. If a local file appears to be an archive, its contents are\n extracted and added instead of the archive file itself."
copyDescription = "Copies the contents of a file, URL, or directory into a container's working\n directory"
@@ -13,6 +20,7 @@ var (
Name: "add",
Usage: "Add content to the container",
Description: addDescription,
Flags: addAndCopyFlags,
Action: addCmd,
ArgsUsage: "CONTAINER-NAME-OR-ID [[FILE | DIRECTORY | URL] ...] [DESTINATION]",
}
@@ -21,6 +29,7 @@ var (
Name: "copy",
Usage: "Copy content into the container",
Description: copyDescription,
Flags: addAndCopyFlags,
Action: copyCmd,
ArgsUsage: "CONTAINER-NAME-OR-ID [[FILE | DIRECTORY | URL] ...] [DESTINATION]",
}
@@ -34,6 +43,10 @@ func addAndCopyCmd(c *cli.Context, extractLocalArchives bool) error {
name := args[0]
args = args.Tail()
if err := validateFlags(c, addAndCopyFlags); err != nil {
return err
}
// If list is greater then one, the last item is the destination
dest := ""
size := len(args)
@@ -52,8 +65,11 @@ func addAndCopyCmd(c *cli.Context, extractLocalArchives bool) error {
return errors.Wrapf(err, "error reading build container %q", name)
}
err = builder.Add(dest, extractLocalArchives, args...)
if err != nil {
options := buildah.AddAndCopyOptions{
Chown: c.String("chown"),
}
if err := builder.Add(dest, extractLocalArchives, options, args...); err != nil {
return errors.Wrapf(err, "error adding content to container %q", builder.Container)
}

View File

@@ -13,6 +13,10 @@ import (
var (
budFlags = []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringSliceFlag{
Name: "build-arg",
Usage: "`argument=value` to supply to the builder",
@@ -56,7 +60,7 @@ var (
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "Require HTTPS and verify certificates when accessing the registry",
Usage: "require HTTPS and verify certificates when accessing the registry",
},
}
@@ -190,6 +194,7 @@ func budCmd(c *cli.Context) error {
Runtime: c.String("runtime"),
RuntimeArgs: c.StringSlice("runtime-flag"),
OutputFormat: format,
AuthFilePath: c.String("authfile"),
}
if !c.Bool("quiet") {
options.ReportWriter = os.Stderr

View File

@@ -65,9 +65,10 @@ func openBuilders(store storage.Store) (builders []*buildah.Builder, err error)
return buildah.OpenAllBuilders(store)
}
func openImage(store storage.Store, name string) (builder *buildah.Builder, err error) {
func openImage(sc *types.SystemContext, store storage.Store, name string) (builder *buildah.Builder, err error) {
options := buildah.ImportFromImageOptions{
Image: name,
Image: name,
SystemContext: sc,
}
builder, err = buildah.ImportBuilderFromImage(store, options)
if err != nil {
@@ -82,7 +83,7 @@ func openImage(store storage.Store, name string) (builder *buildah.Builder, err
func getDateAndDigestAndSize(image storage.Image, store storage.Store) (time.Time, string, int64, error) {
created := time.Time{}
is.Transport.SetStore(store)
storeRef, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
storeRef, err := is.Transport.ParseStoreReference(store, image.ID)
if err != nil {
return created, "", -1, err
}
@@ -133,6 +134,15 @@ func systemContextFromOptions(c *cli.Context) (*types.SystemContext, error) {
if c.IsSet("signature-policy") {
ctx.SignaturePolicyPath = c.String("signature-policy")
}
if c.IsSet("authfile") {
ctx.AuthFilePath = c.String("authfile")
}
if c.GlobalIsSet("registries-conf") {
ctx.SystemRegistriesConfPath = c.GlobalString("registries-conf")
}
if c.GlobalIsSet("registries-conf-dir") {
ctx.RegistriesDirPath = c.GlobalString("registries-conf-dir")
}
return ctx, nil
}

View File

@@ -11,6 +11,10 @@ import (
var (
fromFlags = []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringFlag{
Name: "cert-dir",
Value: "",
@@ -43,7 +47,7 @@ var (
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "Require HTTPS and verify certificates when accessing the registry",
Usage: "require HTTPS and verify certificates when accessing the registry",
},
}
fromDescription = "Creates a new working container, either from scratch or using a specified\n image as a starting point"
@@ -59,7 +63,6 @@ var (
)
func fromCmd(c *cli.Context) error {
args := c.Args()
if len(args) == 0 {
return errors.Errorf("an image name (or \"scratch\") must be specified")

View File

@@ -178,7 +178,7 @@ func setFilterDate(store storage.Store, images []storage.Image, imgName string)
for _, name := range image.Names {
if matchesReference(name, imgName) {
// Set the date to this image
ref, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
ref, err := is.Transport.ParseStoreReference(store, image.ID)
if err != nil {
return time.Time{}, fmt.Errorf("error parsing reference to image %q: %v", image.ID, err)
}
@@ -290,7 +290,7 @@ func matchesDangling(name string, dangling string) bool {
}
func matchesLabel(image storage.Image, store storage.Store, label string) bool {
storeRef, err := is.Transport.ParseStoreReference(store, "@"+image.ID)
storeRef, err := is.Transport.ParseStoreReference(store, image.ID)
if err != nil {
return false
}
@@ -322,19 +322,13 @@ func matchesLabel(image storage.Image, store storage.Store, label string) bool {
// Returns true if the image was created since the filter image. Returns
// false otherwise
func matchesBeforeImage(image storage.Image, name string, params *filterParams) bool {
if image.Created.IsZero() || image.Created.Before(params.beforeDate) {
return true
}
return false
return image.Created.IsZero() || image.Created.Before(params.beforeDate)
}
// Returns true if the image was created since the filter image. Returns
// false otherwise
func matchesSinceImage(image storage.Image, name string, params *filterParams) bool {
if image.Created.IsZero() || image.Created.After(params.sinceDate) {
return true
}
return false
return image.Created.IsZero() || image.Created.After(params.sinceDate)
}
func matchesID(imageID, argID string) bool {

View File

@@ -56,6 +56,11 @@ func inspectCmd(c *cli.Context) error {
return err
}
systemContext, err := systemContextFromOptions(c)
if err != nil {
return errors.Wrapf(err, "error building system context")
}
format := defaultFormat
if c.String("format") != "" {
format = c.String("format")
@@ -76,13 +81,13 @@ func inspectCmd(c *cli.Context) error {
if c.IsSet("type") {
return errors.Wrapf(err, "error reading build container %q", name)
}
builder, err = openImage(store, name)
builder, err = openImage(systemContext, store, name)
if err != nil {
return errors.Wrapf(err, "error reading build object %q", name)
}
}
case inspectTypeImage:
builder, err = openImage(store, name)
builder, err = openImage(systemContext, store, name)
if err != nil {
return errors.Wrapf(err, "error reading image %q", name)
}
@@ -91,7 +96,7 @@ func inspectCmd(c *cli.Context) error {
}
if c.IsSet("format") {
return t.Execute(os.Stdout, builder)
return t.Execute(os.Stdout, buildah.GetBuildInfo(builder))
}
b, err := json.MarshalIndent(builder, "", " ")

View File

@@ -33,6 +33,14 @@ func main() {
Name: "debug",
Usage: "print debugging information",
},
cli.StringFlag{
Name: "registries-conf",
Usage: "path to registries.conf file (not usually used)",
},
cli.StringFlag{
Name: "registries-conf-dir",
Usage: "path to registries.conf.d directory (not usually used)",
},
cli.StringFlag{
Name: "root",
Usage: "storage root dir",

View File

@@ -46,7 +46,7 @@ func mountCmd(c *cli.Context) error {
if err != nil {
return errors.Wrapf(err, "error reading build container %q", name)
}
mountPoint, err := builder.Mount("")
mountPoint, err := builder.Mount(builder.MountLabel)
if err != nil {
return errors.Wrapf(err, "error mounting %q container %q", name, builder.Container)
}

View File

@@ -5,9 +5,11 @@ import (
"os"
"strings"
"github.com/containers/image/manifest"
"github.com/containers/image/transports"
"github.com/containers/image/transports/alltransports"
"github.com/containers/storage/pkg/archive"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/projectatomic/buildah"
"github.com/urfave/cli"
@@ -15,6 +17,10 @@ import (
var (
pushFlags = []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringFlag{
Name: "cert-dir",
Value: "",
@@ -29,6 +35,10 @@ var (
Name: "disable-compression, D",
Usage: "don't compress layers",
},
cli.StringFlag{
Name: "format, f",
Usage: "manifest type (oci, v2s1, or v2s2) to use when saving image using the 'dir:' transport (default is manifest type of source)",
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "don't output progress information when pushing images",
@@ -39,7 +49,7 @@ var (
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "Require HTTPS and verify certificates when accessing the registry",
Usage: "require HTTPS and verify certificates when accessing the registry",
},
}
pushDescription = fmt.Sprintf(`
@@ -104,8 +114,23 @@ func pushCmd(c *cli.Context) error {
return errors.Wrapf(err, "error building system context")
}
var manifestType string
if c.IsSet("format") {
switch c.String("format") {
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
case "v2s1":
manifestType = manifest.DockerV2Schema1SignedMediaType
case "v2s2", "docker":
manifestType = manifest.DockerV2Schema2MediaType
default:
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
}
}
options := buildah.PushOptions{
Compression: compress,
ManifestType: manifestType,
SignaturePolicyPath: c.String("signature-policy"),
Store: store,
SystemContext: systemContext,

View File

@@ -210,12 +210,12 @@ func storageImageID(store storage.Store, id string) (types.ImageReference, error
if img, err := store.Image(id); err == nil && img != nil {
imageID = img.ID
}
if ref, err := is.Transport.ParseStoreReference(store, "@"+imageID); err == nil {
if ref, err := is.Transport.ParseStoreReference(store, imageID); err == nil {
if img, err2 := ref.NewImage(nil); err2 == nil {
img.Close()
return ref, nil
}
return nil, errors.Wrapf(err, "error confirming presence of storage image reference %q", transports.ImageName(ref))
}
return nil, errors.Wrapf(err, "error parsing %q as a storage image reference: %v", "@"+id)
return nil, errors.Wrapf(err, "error parsing %q as a storage image reference: %v", id)
}

View File

@@ -79,10 +79,12 @@ func runCmd(c *cli.Context) error {
Args: c.StringSlice("runtime-flag"),
}
if c.Bool("tty") {
options.Terminal = buildah.WithTerminal
} else {
options.Terminal = buildah.WithoutTerminal
if c.IsSet("tty") {
if c.Bool("tty") {
options.Terminal = buildah.WithTerminal
} else {
options.Terminal = buildah.WithoutTerminal
}
}
for _, volumeSpec := range c.StringSlice("volume") {

169
commit.go
View File

@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"syscall"
"time"
cp "github.com/containers/image/copy"
@@ -19,17 +20,6 @@ import (
"github.com/sirupsen/logrus"
)
var (
// gzippedEmptyLayer is a gzip-compressed version of an empty tar file (just 1024 zero bytes). This
// comes from github.com/docker/distribution/manifest/schema1/config_builder.go by way of
// github.com/containers/image/image/docker_schema2.go; there is a non-zero embedded timestamp; we could
// zero that, but that would just waste storage space in registries, so lets use the same values.
gzippedEmptyLayer = []byte{
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
}
)
// CommitOptions can be used to alter how an image is committed.
type CommitOptions struct {
// PreferredManifestType is the preferred type of image manifest. The
@@ -80,6 +70,9 @@ type PushOptions struct {
// github.com/containers/image/types SystemContext to hold credentials
// and other authentication/authorization information.
SystemContext *types.SystemContext
// ManifestType is the format to use when saving the imge using the 'dir' transport
// possible options are oci, v2s1, and v2s2
ManifestType string
}
// shallowCopy copies the most recent layer, the configuration, and the manifest from one image to another.
@@ -87,7 +80,7 @@ type PushOptions struct {
// almost any other destination has higher expectations.
// We assume that "dest" is a reference to a local image (specifically, a containers/image/storage.storageReference),
// and will fail if it isn't.
func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReference, systemContext *types.SystemContext) error {
func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReference, systemContext *types.SystemContext, compression archive.Compression) error {
var names []string
// Read the target image name.
if dest.DockerReference() != nil {
@@ -103,10 +96,34 @@ func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReferenc
if err != nil {
return errors.Wrapf(err, "error opening image %q for writing", transports.ImageName(dest))
}
// Write an empty filesystem layer, because the image layer requires at least one.
_, err = destImage.PutBlob(bytes.NewReader(gzippedEmptyLayer), types.BlobInfo{Size: int64(len(gzippedEmptyLayer))})
// Look up the container's read-write layer.
container, err := b.store.Container(b.ContainerID)
if err != nil {
return errors.Wrapf(err, "error writing dummy layer for image %q", transports.ImageName(dest))
return errors.Wrapf(err, "error reading information about working container %q", b.ContainerID)
}
// Extract the read-write layer's contents, using whatever compression the container image used to
// calculate the blob sum in the manifest.
switch compression {
case archive.Gzip:
logrus.Debugf("extracting layer %q with gzip", container.LayerID)
case archive.Bzip2:
// Until the image specs define a media type for bzip2-compressed layers, even if we know
// how to decompress them, we can't try to compress layers with bzip2.
return errors.Wrapf(syscall.ENOTSUP, "media type for bzip2-compressed layers is not defined")
default:
logrus.Debugf("extracting layer %q with unknown compressor(?)", container.LayerID)
}
diffOptions := &storage.DiffOptions{
Compression: &compression,
}
layerDiff, err := b.store.Diff("", container.LayerID, diffOptions)
if err != nil {
return errors.Wrapf(err, "error reading layer %q from source image %q", container.LayerID, transports.ImageName(src))
}
defer layerDiff.Close()
// Write a copy of the layer as a blob, for the new image to reference.
if _, err = destImage.PutBlob(layerDiff, types.BlobInfo{Digest: "", Size: -1}); err != nil {
return errors.Wrapf(err, "error creating new read-only layer from container %q", b.ContainerID)
}
// Read the newly-generated configuration blob.
config, err := srcImage.ConfigBlob()
@@ -122,11 +139,10 @@ func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReferenc
Digest: digest.Canonical.FromBytes(config),
Size: int64(len(config)),
}
_, err = destImage.PutBlob(bytes.NewReader(config), configBlobInfo)
if err != nil && len(config) > 0 {
if _, err = destImage.PutBlob(bytes.NewReader(config), configBlobInfo); err != nil {
return errors.Wrapf(err, "error writing image configuration for temporary copy of %q", transports.ImageName(dest))
}
// Read the newly-generated, mostly fake, manifest.
// Read the newly-generated manifest, which already contains a layer entry for the read-write layer.
manifest, _, err := srcImage.Manifest()
if err != nil {
return errors.Wrapf(err, "error reading new manifest for image %q", transports.ImageName(dest))
@@ -145,79 +161,9 @@ func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReferenc
if err != nil {
return errors.Wrapf(err, "error closing new image %q", transports.ImageName(dest))
}
// Locate the new image in the lower-level API. Extract its items.
destImg, err := is.Transport.GetStoreImage(b.store, dest)
image, err := is.Transport.GetStoreImage(b.store, dest)
if err != nil {
return errors.Wrapf(err, "error locating new image %q", transports.ImageName(dest))
}
items, err := b.store.ListImageBigData(destImg.ID)
if err != nil {
return errors.Wrapf(err, "error reading list of named data for image %q", destImg.ID)
}
bigdata := make(map[string][]byte)
for _, itemName := range items {
var data []byte
data, err = b.store.ImageBigData(destImg.ID, itemName)
if err != nil {
return errors.Wrapf(err, "error reading named data %q for image %q", itemName, destImg.ID)
}
bigdata[itemName] = data
}
// Delete the image so that we can recreate it.
_, err = b.store.DeleteImage(destImg.ID, true)
if err != nil {
return errors.Wrapf(err, "error deleting image %q for rewriting", destImg.ID)
}
// Look up the container's read-write layer.
container, err := b.store.Container(b.ContainerID)
if err != nil {
return errors.Wrapf(err, "error reading information about working container %q", b.ContainerID)
}
parentLayer := ""
// Look up the container's source image's layer, if there is a source image.
if container.ImageID != "" {
img, err2 := b.store.Image(container.ImageID)
if err2 != nil {
return errors.Wrapf(err2, "error reading information about working container %q's source image", b.ContainerID)
}
parentLayer = img.TopLayer
}
// Extract the read-write layer's contents.
layerDiff, err := b.store.Diff(parentLayer, container.LayerID, nil)
if err != nil {
return errors.Wrapf(err, "error reading layer %q from source image %q", container.LayerID, transports.ImageName(src))
}
defer layerDiff.Close()
// Write a copy of the layer for the new image to reference.
layer, _, err := b.store.PutLayer("", parentLayer, []string{}, "", false, layerDiff)
if err != nil {
return errors.Wrapf(err, "error creating new read-only layer from container %q", b.ContainerID)
}
// Create a low-level image record that uses the new layer, discarding the old metadata.
image, err := b.store.CreateImage(destImg.ID, []string{}, layer.ID, "{}", nil)
if err != nil {
err2 := b.store.DeleteLayer(layer.ID)
if err2 != nil {
logrus.Debugf("error removing layer %q: %v", layer, err2)
}
return errors.Wrapf(err, "error creating new low-level image %q", transports.ImageName(dest))
}
logrus.Debugf("(re-)created image ID %q using layer %q", image.ID, layer.ID)
defer func() {
if err != nil {
_, err2 := b.store.DeleteImage(image.ID, true)
if err2 != nil {
logrus.Debugf("error removing image %q: %v", image.ID, err2)
}
}
}()
// Store the configuration and manifest, which are big data items, along with whatever else is there.
for itemName, data := range bigdata {
err = b.store.SetImageBigData(image.ID, itemName, data)
if err != nil {
return errors.Wrapf(err, "error saving data item %q", itemName)
}
logrus.Debugf("saved data item %q to %q", itemName, image.ID)
return errors.Wrapf(err, "error locating just-written image %q", transports.ImageName(dest))
}
// Add the target name(s) to the new image.
if len(names) > 0 {
@@ -234,7 +180,7 @@ func (b *Builder) shallowCopy(dest types.ImageReference, src types.ImageReferenc
// configuration, to a new image in the specified location, and if we know how,
// add any additional tags that were specified.
func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error {
policy, err := signature.DefaultPolicy(getSystemContext(options.SignaturePolicyPath))
policy, err := signature.DefaultPolicy(getSystemContext(options.SystemContext, options.SignaturePolicyPath))
if err != nil {
return errors.Wrapf(err, "error obtaining default signature policy")
}
@@ -244,25 +190,25 @@ func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error
}
defer func() {
if err2 := policyContext.Destroy(); err2 != nil {
logrus.Debugf("error destroying signature polcy context: %v", err2)
logrus.Debugf("error destroying signature policy context: %v", err2)
}
}()
// Check if we're keeping everything in local storage. If so, we can take certain shortcuts.
_, destIsStorage := dest.Transport().(is.StoreTransport)
exporting := !destIsStorage
src, err := b.makeContainerImageRef(options.PreferredManifestType, exporting, options.Compression, options.HistoryTimestamp)
src, err := b.makeImageRef(options.PreferredManifestType, exporting, options.Compression, options.HistoryTimestamp)
if err != nil {
return errors.Wrapf(err, "error computing layer digests and building metadata")
}
if exporting {
// Copy everything.
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext))
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext, ""))
if err != nil {
return errors.Wrapf(err, "error copying layers and metadata")
}
} else {
// Copy only the most recent layer, the configuration, and the manifest.
err = b.shallowCopy(dest, src, getSystemContext(options.SignaturePolicyPath))
err = b.shallowCopy(dest, src, getSystemContext(options.SystemContext, options.SignaturePolicyPath), options.Compression)
if err != nil {
return errors.Wrapf(err, "error copying layer and metadata")
}
@@ -288,7 +234,7 @@ func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error
// Push copies the contents of the image to a new location.
func Push(image string, dest types.ImageReference, options PushOptions) error {
systemContext := getSystemContext(options.SignaturePolicyPath)
systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath)
policy, err := signature.DefaultPolicy(systemContext)
if err != nil {
return errors.Wrapf(err, "error obtaining default signature policy")
@@ -297,38 +243,13 @@ func Push(image string, dest types.ImageReference, options PushOptions) error {
if err != nil {
return errors.Wrapf(err, "error creating new signature policy context")
}
defer func() {
if err2 := policyContext.Destroy(); err2 != nil {
logrus.Debugf("error destroying signature polcy context: %v", err2)
}
}()
importOptions := ImportFromImageOptions{
Image: image,
SignaturePolicyPath: options.SignaturePolicyPath,
}
builder, err := importBuilderFromImage(options.Store, importOptions)
if err != nil {
return errors.Wrap(err, "error importing builder information from image")
}
// Look up the image name and its layer.
ref, err := is.Transport.ParseStoreReference(options.Store, image)
// Look up the image.
src, err := is.Transport.ParseStoreReference(options.Store, image)
if err != nil {
return errors.Wrapf(err, "error parsing reference to image %q", image)
}
img, err := is.Transport.GetStoreImage(options.Store, ref)
if err != nil {
return errors.Wrapf(err, "error locating image %q", image)
}
// Give the image we're producing the same ancestors as its source image.
builder.FromImage = builder.Docker.ContainerConfig.Image
builder.FromImageID = string(builder.Docker.Parent)
// Prep the layers and manifest for export.
src, err := builder.makeImageImageRef(options.Compression, img.Names, img.TopLayer, nil)
if err != nil {
return errors.Wrapf(err, "error recomputing layer digests and building metadata")
}
// Copy everything.
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext))
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext, options.ManifestType))
if err != nil {
return errors.Wrapf(err, "error copying layers and metadata")
}

View File

@@ -7,16 +7,20 @@ import (
"github.com/containers/image/types"
)
func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext) *cp.Options {
func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext, manifestType string) *cp.Options {
return &cp.Options{
ReportWriter: reportWriter,
SourceCtx: sourceSystemContext,
DestinationCtx: destinationSystemContext,
ReportWriter: reportWriter,
SourceCtx: sourceSystemContext,
DestinationCtx: destinationSystemContext,
ForceManifestMIMEType: manifestType,
}
}
func getSystemContext(signaturePolicyPath string) *types.SystemContext {
func getSystemContext(defaults *types.SystemContext, signaturePolicyPath string) *types.SystemContext {
sc := &types.SystemContext{}
if defaults != nil {
*sc = *defaults
}
if signaturePolicyPath != "" {
sc.SignaturePolicyPath = signaturePolicyPath
}

View File

@@ -345,6 +345,7 @@ return 1
"
local options_with_args="
--authfile
--signature-policy
--runtime
--runtime-flag
@@ -481,8 +482,11 @@ return 1
"
local options_with_args="
--authfile
--cert-dir
--creds
--format
-f
--signature-policy
"
@@ -627,6 +631,7 @@ return 1
"
local options_with_args="
--authfile
--cert-dir
--creds
--name

View File

@@ -25,7 +25,7 @@
%global shortcommit %(c=%{commit}; echo ${c:0:7})
Name: buildah
Version: 0.5
Version: 0.10
Release: 1.git%{shortcommit}%{?dist}
Summary: A command line tool used to creating OCI Images
License: ASL 2.0
@@ -43,7 +43,9 @@ BuildRequires: btrfs-progs-devel
BuildRequires: libassuan-devel
BuildRequires: glib2-devel
BuildRequires: ostree-devel
BuildRequires: make
Requires: runc >= 1.0.0-6
Requires: container-selinux
Requires: skopeo-containers
Provides: %{repo} = %{version}-%{release}
@@ -87,6 +89,35 @@ make DESTDIR=%{buildroot} PREFIX=%{_prefix} install install.completions
%{_datadir}/bash-completion/completions/*
%changelog
* Sat Dec 23 2017 Dan Walsh <dwalsh@redhat.com> 0.10-1
- Display Config and Manifest as strings
- Bump containers/image
- Use configured registries to resolve image names
- Update to work with newer image library
- Add --chown option to add/copy commands
* Sat Dec 2 2017 Dan Walsh <dwalsh@redhat.com> 0.9-1
- Allow push to use the image id
- Make sure builtin volumes have the correct label
* Thu Nov 16 2017 Dan Walsh <dwalsh@redhat.com> 0.8-1
- Buildah bud was failing on SELinux machines, this fixes this
- Block access to certain kernel file systems inside of the container
* Thu Nov 16 2017 Dan Walsh <dwalsh@redhat.com> 0.7-1
- Ignore errors when trying to read containers buildah.json for loading SELinux reservations
- Use credentials from kpod login for buildah
* Wed Nov 15 2017 Dan Walsh <dwalsh@redhat.com> 0.6-1
- Adds support for converting manifest types when using the dir transport
- Rework how we do UID resolution in images
- Bump github.com/vbatts/tar-split
- Set option.terminal appropriately in run
* Wed Nov 08 2017 Dan Walsh <dwalsh@redhat.com> 0.5-2
- Bump github.com/vbatts/tar-split
- Fixes CVE That could allow a container image to cause a DOS
* Tue Nov 07 2017 Dan Walsh <dwalsh@redhat.com> 0.5-1
- Add secrets patch to buildah
- Add proper SELinux labeling to buildah run
@@ -117,10 +148,19 @@ make DESTDIR=%{buildroot} PREFIX=%{_prefix} install install.completions
- Turn on --enable-gc when running gometalinter
- rmi: handle truncated image IDs
* Thu Jul 20 2017 Dan Walsh <dwalsh@redhat.com> 0.3.0-1
* Tue Aug 15 2017 Josh Boyer <jwboyer@redhat.com> - 0.3-5.gitb9b2a8a
- Build for s390x as well
* Wed Aug 02 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.3-4.gitb9b2a8a
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild
* Wed Jul 26 2017 Fedora Release Engineering <releng@fedoraproject.org> - 0.3-3.gitb9b2a8a
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
* Thu Jul 20 2017 Dan Walsh <dwalsh@redhat.com> 0.3-2.gitb9b2a8a7e
- Bump for inclusion of OCI 1.0 Runtime and Image Spec
* Tue Jul 18 2017 Dan Walsh <dwalsh@redhat.com> 0.2.0-1
* Tue Jul 18 2017 Dan Walsh <dwalsh@redhat.com> 0.2.0-1.gitac2aad6
- buildah run: Add support for -- ending options parsing
- buildah Add/Copy support for glob syntax
- buildah commit: Add flag to remove containers on commit
@@ -137,7 +177,11 @@ make DESTDIR=%{buildroot} PREFIX=%{_prefix} install install.completions
- buildah version: add command
- buildah run: Handle run without an explicit command correctly
- Ensure volume points get created, and with perms
- buildah containers: Add a -a/--all option
- buildah containers: Add a -a/--all option
* Wed Jun 14 2017 Dan Walsh <dwalsh@redhat.com> 0.1.0-2.git597d2ab9
- Release Candidate 1
- All features have now been implemented.
* Fri Apr 14 2017 Dan Walsh <dwalsh@redhat.com> 0.0.1-1.git7a0a5333
- First package for Fedora

View File

@@ -13,10 +13,18 @@ appears to be an archive, its contents are extracted and added instead of the
archive file itself. If a local directory is specified as a source, its
*contents* are copied to the destination.
## OPTIONS
**--chown** *owner*:*group*
Sets the user and group ownership of the destination content.
## EXAMPLE
buildah add containerID '/myapp/app.conf' '/myapp/app.conf'
buildah add --chown myuser:mygroup containerID '/myapp/app.conf' '/myapp/app.conf'
buildah add containerID '/home/myuser/myproject.go'
buildah add containerID '/home/myuser/myfiles.tar' '/tmp'

View File

@@ -14,6 +14,11 @@ to a temporary location.
## OPTIONS
**--authfile** *path*
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `kpod login`.
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
**--build-arg** *arg=value*
Specifies a build argument and its value, which will be interpolated in
@@ -93,4 +98,4 @@ buildah bud --tls-verify=true -t imageName -f Dockerfile.simple
buildah bud --tls-verify=false -t imageName .
## SEE ALSO
buildah(1)
buildah(1), kpod-login(1), docker-login(1)

View File

@@ -11,10 +11,18 @@ Copies the contents of a file, URL, or a directory to a container's working
directory or a specified location in the container. If a local directory is
specified as a source, its *contents* are copied to the destination.
## OPTIONS
**--chown** *owner*:*group*
Sets the user and group ownership of the destination content.
## EXAMPLE
buildah copy containerID '/myapp/app.conf' '/myapp/app.conf'
buildah copy --chown myuser:mygroup containerID '/myapp/app.conf' '/myapp/app.conf'
buildah copy containerID '/home/myuser/myproject.go'
buildah copy containerID '/home/myuser/myfiles.tar' '/tmp'

View File

@@ -17,7 +17,7 @@ Multiple transports are supported:
An existing local directory _path_ retrieving the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
**docker://**_docker-reference_ (Default)
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`.
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(kpod login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
**docker-archive:**_path_
An image is retrieved as a `docker load` formatted file.
@@ -36,6 +36,11 @@ The container ID of the container that was created. On error, -1 is returned an
## OPTIONS
**--authfile** *path*
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `kpod login`.
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
**--cert-dir** *path*
Use certificates at *path* (*.crt, *.cert, *.key) to connect to the registry
@@ -86,5 +91,7 @@ buildah from myregistry/myrepository/imagename:imagetag --tls-verify=false
buildah from myregistry/myrepository/imagename:imagetag --creds=myusername:mypassword --cert-dir ~/auth
buildah from myregistry/myrepository/imagename:imagetag --authfile=/tmp/auths/myauths.json
## SEE ALSO
buildah(1)
buildah(1), kpod-login(1), docker-login(1)

View File

@@ -24,7 +24,7 @@ Image stored in local container/storage
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
**docker://**_docker-reference_
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$HOME/.docker/config.json`, which is set e.g. using `(docker login)`.
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(kpod login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`.
**docker-archive:**_path_[**:**_docker-reference_]
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
@@ -40,6 +40,11 @@ Image stored in local container/storage
## OPTIONS
**--authfile** *path*
Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `kpod login`.
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`.
**--cert-dir** *path*
Use certificates at *path* (*.crt, *.cert, *.key) to connect to the registry
@@ -52,6 +57,10 @@ The username[:password] to use to authenticate with the registry if required.
Don't compress copies of filesystem layers which will be pushed.
**--format, -f**
Manifest Type (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)
**--quiet**
When writing the output image, suppress progress output.
@@ -80,6 +89,10 @@ This example extracts the imageID image to a container registry named registry.e
`# buildah push imageID docker://registry.example.com/repository:tag`
This example extracts the imageID image to a private container registry named registry.example.com with authentication from /tmp/auths/myauths.json.
`# buildah push --authfile /tmp/auths/myauths.json imageID docker://registry.example.com/repository:tag`
This example extracts the imageID image and puts into the local docker container store.
`# buildah push imageID docker-daemon:image:tag`
@@ -91,4 +104,4 @@ This example extracts the imageID image and puts it into the registry on the loc
`# buildah push --cert-dir ~/auth --tls-verify=true --creds=username:password imageID docker://localhost:5000/my-imageID`
## SEE ALSO
buildah(1)
buildah(1), kpod-login(1), docker-login(1)

View File

@@ -24,12 +24,28 @@ Print debugging information
**--default-mounts-file**
path to default mounts file (default path: "/usr/share/containers/mounts.conf")
Path to default mounts file (default path: "/usr/share/containers/mounts.conf")
**--help, -h**
Show help
**--registries-conf** *path*
Pathname of the configuration file which specifies which registries should be
consulted when completing image names which do not include a registry or domain
portion. It is not recommended that this option be used, as the default
behavior of using the system-wide configuration
(*/etc/containers/registries.conf*) is most often preferred.
**--registries-conf-dir** *path*
Pathname of the directory which contains configuration snippets which specify
registries which should be consulted when completing image names which do not
include a registry or domain portion. It is not recommended that this option
be used, as the default behavior of using the system-wide configuration
(*/etc/containers/registries.d*) is most often preferred.
**--root** **value**
Storage root dir (default: "/var/lib/containers/storage")

View File

@@ -1,3 +1,5 @@
![buildah logo](https://cdn.rawgit.com/projectatomic/buildah/master/logos/buildah-logo_large.png)
# Buildah Tutorial 1
## Building OCI container images

View File

@@ -0,0 +1,134 @@
![buildah logo](https://cdn.rawgit.com/projectatomic/buildah/master/logos/buildah-logo_large.png)
# Buildah Tutorial 2
## Using Buildah with container registries
The purpose of this tutorial is to demonstrate how Buildah can be used to move OCI compliant images in and out of private or public registries.
In the [first tutorial](https://github.com/projectatomic/buildah/blob/master/docs/tutorials/01-intro.md) we built an image from scratch that we called `fedora-bashecho` and we pushed it to a local Docker repository using the `docker-daemon` protocol. We are going to use the same image to push to a private Docker registry.
First we must pull down a registry. As a shortcut we will save the container name that is returned from the `buildah from` command, into a bash variable called `registry`. This is just like we did in Tutorial 1:
# registry=$(buildah from registry)
It is worth pointing out that the `from` command can also use other protocols beyond the default (and implicity assumed) order that first looks in local containers-storage (containers-storage:) and then looks in the Docker hub (docker:). For example, if you already had a registry container image in a local Docker registry then you could use the following:
# registry=$(buildah from docker-daemon:registry:latest)
Then we need to start the registry. You should start the registry in a separate shell and leave it running there:
# buildah run $registry
If you would like to see more details as to what is going on inside the registry, especially if you are having problems with the registry, you can run the registry container in debug mode as follows:
# buildah --debug run $registry
You can use `--debug` on any Buildah command.
The registry is running and is waiting for requests to process. Notice that this registry is a Docker registry that we pulled from Docker hub and we are running it for this example using `buildah run`. There is no Docker daemon running at this time.
Let's push our image to the private registry. By default, Buildah is set up to expect secure connections to a registry. Therefore we will need to turn the TLS verification off using the `--tls-verify` flag. We also need to tell Buildah that the registry is on this local host ( i.e. localhost) and listening on port 5000. Similar to what you'd expect to do on multi-tenant Docker hub, we will explicitly specify that the registry is to store the image under the `ipbabble` repository - so as not to clash with other users' similarly named images.
# buildah push --tls-verify=false fedora-bashecho docker://localhost:5000/ipbabble/fedora-bashecho:latest
[Skopeo](https://github.com/projectatomic/skopeo) is a ProjectAtomic tool that was created to inspect images in registries without having to pull the image from the registry. It has grown to have many other uses. We will verify that the image has been stored by using Skopeo to inspect the image in the registry:
# skopeo inspect --tls-verify=false docker://localhost:5000/ipbabble/fedora-bashecho:latest
{
"Name": "localhost:5000/ipbabble/fedora-bashecho",
"Digest": "sha256:6806f9385f97bc09f54b5c0ef583e58c3bc906c8c0b3e693d8782d0a0acf2137",
"RepoTags": [
"latest"
],
"Created": "2017-12-05T21:38:12.311901938Z",
"DockerVersion": "",
"Labels": {
"name": "fedora-bashecho"
},
"Architecture": "amd64",
"Os": "linux",
"Layers": [
"sha256:0cb7556c714767b8da6e0299cbeab765abaddede84769475c023785ae66d10ca"
]
}
We can verify that it is still portable with Docker by starting Docker again, as we did in the first tutorial. Then we can pull down the image and starting the container using Docker:
# systemctl start docker
# docker pull localhost:5000/ipbabble/fedora-bashecho
Using default tag: latest
Trying to pull repository localhost:5000/ipbabble/fedora-bashecho ...
sha256:6806f9385f97bc09f54b5c0ef583e58c3bc906c8c0b3e693d8782d0a0acf2137: Pulling from localhost:5000/ipbabble/fedora-bashecho
0cb7556c7147: Pull complete
Digest: sha256:6806f9385f97bc09f54b5c0ef583e58c3bc906c8c0b3e693d8782d0a0acf2137
Status: Downloaded newer image for localhost:5000/ipbabble/fedora-bashecho:latest
# docker run localhost:5000/ipbabble/fedora-bashecho
This is a new container named ipbabble [ 0 ]
This is a new container named ipbabble [ 1 ]
This is a new container named ipbabble [ 2 ]
This is a new container named ipbabble [ 3 ]
This is a new container named ipbabble [ 4 ]
This is a new container named ipbabble [ 5 ]
This is a new container named ipbabble [ 6 ]
This is a new container named ipbabble [ 7 ]
This is a new container named ipbabble [ 8 ]
This is a new container named ipbabble [ 9 ]
# systemctl stop docker
Pushing to Docker hub is just as easy. Of course you must have an account with credentials. In this example I'm using a Docker hub API key, which has the form "username:password" (example password has been edited for privacy), that I created with my Docker hub account. I use the `--creds` flag to use my API key. I also specify my local image name `fedora-bashecho` as my image source and I use the `docker` protocol with no host or port so that it will look at the default Docker hub registry:
# buildah push --creds ipbabble:5bbb9990-6eeb-1234-af1a-aaa80066887c fedora-bashecho docker://ipbabble/fedora-bashecho:latest
And let's inspect that with Skopeo:
# skopeo inspect --creds ipbabble:5bbb9990-6eeb-1234-af1a-aaa80066887c docker://ipbabble/fedora-bashecho:latest
{
"Name": "docker.io/ipbabble/fedora-bashecho",
"Digest": "sha256:6806f9385f97bc09f54b5c0ef583e58c3bc906c8c0b3e693d8782d0a0acf2137",
"RepoTags": [
"latest"
],
"Created": "2017-12-05T21:38:12.311901938Z",
"DockerVersion": "",
"Labels": {
"name": "fedora-bashecho"
},
"Architecture": "amd64",
"Os": "linux",
"Layers": [
"sha256:0cb7556c714767b8da6e0299cbeab765abaddede84769475c023785ae66d10ca"
]
}
We can use Buildah to pull down the image using the `buildah from` command. But before we do let's clean up our local containers-storage so that we don't have an existing fedora-bashecho - otherwise Buildah will know it already exists and not bother pulling it down.
# buildah images
IMAGE ID IMAGE NAME CREATED AT SIZE
d4cd7d73ee42 docker.io/library/registry:latest Dec 1, 2017 22:15 31.74 MB
e31b0f0b0a63 docker.io/library/fedora-bashecho:latest Dec 5, 2017 21:38 772 B
# buildah rmi fedora-bashecho
untagged: docker.io/library/fedora-bashecho:latest
e31b0f0b0a63e94c5a558d438d7490fab930a282a4736364360ab9b92cb25f3a
# buildah images
IMAGE ID IMAGE NAME CREATED AT SIZE
d4cd7d73ee42 docker.io/library/registry:latest Dec 1, 2017 22:15 31.74 MB
Okay, so we don't have a fedora-bashecho anymore. Let's pull the image from Docker hub:
# buildah from ipbabble/fedora-bashecho
If you don't want to bother doing the remove image step (`rmi`) you can use the flag `--pull-always` to force the image to be pulled again and overwrite any corresponding local image.
Now check that image is in the local containers-storage:
# buildah images
IMAGE ID IMAGE NAME CREATED AT SIZE
d4cd7d73ee42 docker.io/library/registry:latest Dec 1, 2017 22:15 31.74 MB
864871ac1c45 docker.io/ipbabble/fedora-bashecho:latest Dec 5, 2017 21:38 315.4 MB
Success!
If you have any suggestions or issues please post them at the [ProjectAtomic Buildah Issues page](https://github.com/projectatomic/buildah/issues).
For more information on Buildah and how you might contribute please visit the [Buildah home page on Github](https://github.com/projectatomic/buildah).

View File

@@ -0,0 +1,16 @@
![buildah logo](https://cdn.rawgit.com/projectatomic/buildah/master/logos/buildah-logo_large.png)
# Buildah Tutorials
## Links to a number of useful tutorials for the Buildah project.
**[Introduction Tutorial](https://github.com/projectatomic/buildah/tree/master/docs/tutorials/01-intro.md)**
Learn how to build container images compliant with the [Open Container Initiative](https://www.opencontainers.org/) (OCI) [image specification](https://github.com/opencontainers/image-spec) using Buildah.
**[Buildah and Registries Tutorial](https://github.com/projectatomic/buildah/tree/master/docs/tutorials/02-registries-repositories.md)**
Learn how Buildah can be used to move OCI compliant images in and out of private or public registries.

163
image.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/manifest"
is "github.com/containers/image/storage"
"github.com/containers/image/types"
"github.com/containers/storage"
@@ -43,7 +42,6 @@ type containerImageRef struct {
name reference.Named
names []string
layerID string
addHistory bool
oconfig []byte
dconfig []byte
created time.Time
@@ -59,7 +57,6 @@ type containerImageSource struct {
store storage.Store
layerID string
names []string
addHistory bool
compression archive.Compression
config []byte
configDigest digest.Digest
@@ -68,12 +65,12 @@ type containerImageSource struct {
exporting bool
}
func (i *containerImageRef) NewImage(sc *types.SystemContext) (types.Image, error) {
func (i *containerImageRef) NewImage(sc *types.SystemContext) (types.ImageCloser, error) {
src, err := i.NewImageSource(sc)
if err != nil {
return nil, err
}
return image.FromSource(src)
return image.FromSource(sc, src)
}
func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.ImageSource, err error) {
@@ -128,11 +125,14 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
if err != nil {
return nil, err
}
created := i.created
oimage.Created = &created
dimage := docker.V2Image{}
err = json.Unmarshal(i.dconfig, &dimage)
if err != nil {
return nil, err
}
dimage.Created = created
// Start building manifests.
omanifest := v1.Manifest{
@@ -164,9 +164,39 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
// Extract each layer and compute its digests, both compressed (if requested) and uncompressed.
for _, layerID := range layers {
// The default layer media type assumes no compression.
omediaType := v1.MediaTypeImageLayer
dmediaType := docker.V2S2MediaTypeUncompressedLayer
// Figure out which media type we want to call this. Assume no compression.
// If we're not re-exporting the data, reuse the blobsum and diff IDs.
if !i.exporting && layerID != i.layerID {
layer, err2 := i.store.Layer(layerID)
if err2 != nil {
return nil, errors.Wrapf(err, "unable to locate layer %q", layerID)
}
if layer.UncompressedDigest == "" {
return nil, errors.Errorf("unable to look up size of layer %q", layerID)
}
layerBlobSum := layer.UncompressedDigest
layerBlobSize := layer.UncompressedSize
// Note this layer in the manifest, using the uncompressed blobsum.
olayerDescriptor := v1.Descriptor{
MediaType: omediaType,
Digest: layerBlobSum,
Size: layerBlobSize,
}
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
dlayerDescriptor := docker.V2S2Descriptor{
MediaType: dmediaType,
Digest: layerBlobSum,
Size: layerBlobSize,
}
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
// Note this layer in the list of diffIDs, again using the uncompressed blobsum.
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, layerBlobSum)
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, layerBlobSum)
continue
}
// Figure out if we need to change the media type, in case we're using compression.
if i.compression != archive.Uncompressed {
switch i.compression {
case archive.Gzip:
@@ -181,46 +211,18 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
logrus.Debugf("compressing layer %q with unknown compressor(?)", layerID)
}
}
// If we're not re-exporting the data, just fake up layer and diff IDs for the manifest.
if !i.exporting {
fakeLayerDigest := digest.NewDigestFromHex(digest.Canonical.String(), layerID)
// Add a note in the manifest about the layer. The blobs should be identified by their
// possibly-compressed blob digests, but just use the layer IDs here.
olayerDescriptor := v1.Descriptor{
MediaType: omediaType,
Digest: fakeLayerDigest,
Size: -1,
}
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
dlayerDescriptor := docker.V2S2Descriptor{
MediaType: dmediaType,
Digest: fakeLayerDigest,
Size: -1,
}
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
// Add a note about the diffID, which should be uncompressed digest of the blob, but
// just use the layer ID here.
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, fakeLayerDigest)
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, fakeLayerDigest)
continue
}
// Start reading the layer.
rc, err := i.store.Diff("", layerID, nil)
noCompression := archive.Uncompressed
diffOptions := &storage.DiffOptions{
Compression: &noCompression,
}
rc, err := i.store.Diff("", layerID, diffOptions)
if err != nil {
return nil, errors.Wrapf(err, "error extracting layer %q", layerID)
}
defer rc.Close()
// Set up to decompress the layer, in case it's coming out compressed. Due to implementation
// differences, the result may not match the digest the blob had when it was originally imported,
// so we have to recompute all of this anyway if we want to be sure the digests we use will be
// correct.
uncompressed, err := archive.DecompressStream(rc)
if err != nil {
return nil, errors.Wrapf(err, "error decompressing layer %q", layerID)
}
defer uncompressed.Close()
srcHasher := digest.Canonical.Digester()
reader := io.TeeReader(uncompressed, srcHasher.Hash())
reader := io.TeeReader(rc, srcHasher.Hash())
// Set up to write the possibly-recompressed blob.
layerFile, err := os.OpenFile(filepath.Join(path, "layer"), os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
@@ -229,7 +231,7 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
destHasher := digest.Canonical.Digester()
counter := ioutils.NewWriteCounter(layerFile)
multiWriter := io.MultiWriter(counter, destHasher.Hash())
// Compress the layer, if we're compressing it.
// Compress the layer, if we're recompressing it.
writer, err := archive.CompressStream(multiWriter, i.compression)
if err != nil {
return nil, errors.Wrapf(err, "error compressing layer %q", layerID)
@@ -267,28 +269,26 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
Size: size,
}
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
// Add a note about the diffID, which is always an uncompressed value.
// Add a note about the diffID, which is always the layer's uncompressed digest.
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, srcHasher.Digest())
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, srcHasher.Digest())
}
if i.addHistory {
// Build history notes in the image configurations.
onews := v1.History{
Created: &i.created,
CreatedBy: i.createdBy,
Author: oimage.Author,
EmptyLayer: false,
}
oimage.History = append(oimage.History, onews)
dnews := docker.V2S2History{
Created: i.created,
CreatedBy: i.createdBy,
Author: dimage.Author,
EmptyLayer: false,
}
dimage.History = append(dimage.History, dnews)
// Build history notes in the image configurations.
onews := v1.History{
Created: &i.created,
CreatedBy: i.createdBy,
Author: oimage.Author,
EmptyLayer: false,
}
oimage.History = append(oimage.History, onews)
dnews := docker.V2S2History{
Created: i.created,
CreatedBy: i.createdBy,
Author: dimage.Author,
EmptyLayer: false,
}
dimage.History = append(dimage.History, dnews)
// Encode the image configuration blob.
oconfig, err := json.Marshal(&oimage)
@@ -347,7 +347,6 @@ func (i *containerImageRef) NewImageSource(sc *types.SystemContext) (src types.I
store: i.store,
layerID: i.layerID,
names: i.names,
addHistory: i.addHistory,
compression: i.compression,
config: config,
configDigest: digest.Canonical.FromBytes(config),
@@ -402,16 +401,22 @@ func (i *containerImageSource) Reference() types.ImageReference {
return i.ref
}
func (i *containerImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
func (i *containerImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if instanceDigest != nil && *instanceDigest != digest.FromBytes(i.manifest) {
return nil, errors.Errorf("TODO")
}
return nil, nil
}
func (i *containerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return []byte{}, "", errors.Errorf("TODO")
func (i *containerImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
if instanceDigest != nil && *instanceDigest != digest.FromBytes(i.manifest) {
return nil, "", errors.Errorf("TODO")
}
return i.manifest, i.manifestType, nil
}
func (i *containerImageSource) GetManifest() ([]byte, string, error) {
return i.manifest, i.manifestType, nil
func (i *containerImageSource) LayerInfosForCopy() []types.BlobInfo {
return nil
}
func (i *containerImageSource) GetBlob(blob types.BlobInfo) (reader io.ReadCloser, size int64, err error) {
@@ -445,10 +450,14 @@ func (i *containerImageSource) GetBlob(blob types.BlobInfo) (reader io.ReadClose
return ioutils.NewReadCloserWrapper(layerFile, closer), size, nil
}
func (b *Builder) makeImageRef(manifestType string, exporting, addHistory bool, compress archive.Compression, names []string, layerID string, historyTimestamp *time.Time) (types.ImageReference, error) {
func (b *Builder) makeImageRef(manifestType string, exporting bool, compress archive.Compression, historyTimestamp *time.Time) (types.ImageReference, error) {
var name reference.Named
if len(names) > 0 {
if parsed, err := reference.ParseNamed(names[0]); err == nil {
container, err := b.store.Container(b.ContainerID)
if err != nil {
return nil, errors.Wrapf(err, "error locating container %q", b.ContainerID)
}
if len(container.Names) > 0 {
if parsed, err2 := reference.ParseNamed(container.Names[0]); err2 == nil {
name = parsed
}
}
@@ -471,9 +480,8 @@ func (b *Builder) makeImageRef(manifestType string, exporting, addHistory bool,
store: b.store,
compression: compress,
name: name,
names: names,
layerID: layerID,
addHistory: addHistory,
names: container.Names,
layerID: container.LayerID,
oconfig: oconfig,
dconfig: dconfig,
created: created,
@@ -484,18 +492,3 @@ func (b *Builder) makeImageRef(manifestType string, exporting, addHistory bool,
}
return ref, nil
}
func (b *Builder) makeContainerImageRef(manifestType string, exporting bool, compress archive.Compression, historyTimestamp *time.Time) (types.ImageReference, error) {
if manifestType == "" {
manifestType = OCIv1ImageManifest
}
container, err := b.store.Container(b.ContainerID)
if err != nil {
return nil, errors.Wrapf(err, "error locating container %q", b.ContainerID)
}
return b.makeImageRef(manifestType, exporting, true, compress, container.Names, container.LayerID, historyTimestamp)
}
func (b *Builder) makeImageImageRef(compress archive.Compression, names []string, layerID string, historyTimestamp *time.Time) (types.ImageReference, error) {
return b.makeImageRef(manifest.GuessMIMEType(b.Manifest), true, false, compress, names, layerID, historyTimestamp)
}

View File

@@ -105,6 +105,7 @@ type BuildOptions struct {
// configuration data.
// Accepted values are OCIv1ImageFormat and Dockerv2ImageFormat.
OutputFormat string
AuthFilePath string
}
// Executor is a buildah-based implementation of the imagebuilder.Executor
@@ -138,11 +139,14 @@ type Executor struct {
reportWriter io.Writer
}
func makeSystemContext(signaturePolicyPath string, skipTLSVerify bool) *types.SystemContext {
func makeSystemContext(signaturePolicyPath, authFilePath string, skipTLSVerify bool) *types.SystemContext {
sc := &types.SystemContext{}
if signaturePolicyPath != "" {
sc.SignaturePolicyPath = signaturePolicyPath
}
if authFilePath != "" {
sc.AuthFilePath = authFilePath
}
sc.DockerInsecureSkipTLSVerify = skipTLSVerify
return sc
}
@@ -337,7 +341,7 @@ func (b *Executor) Copy(excludes []string, copies ...imagebuilder.Copy) error {
sources = append(sources, filepath.Join(b.contextDir, src))
}
}
if err := b.builder.Add(copy.Dest, copy.Download, sources...); err != nil {
if err := b.builder.Add(copy.Dest, copy.Download, buildah.AddAndCopyOptions{}, sources...); err != nil {
return err
}
}
@@ -423,7 +427,7 @@ func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) {
outputFormat: options.OutputFormat,
additionalTags: options.AdditionalTags,
signaturePolicyPath: options.SignaturePolicyPath,
systemContext: makeSystemContext(options.SignaturePolicyPath, options.SkipTLSVerify),
systemContext: makeSystemContext(options.SignaturePolicyPath, options.AuthFilePath, options.SkipTLSVerify),
volumeCache: make(map[string]string),
volumeCacheInfo: make(map[string]os.FileInfo),
log: options.Log,
@@ -517,7 +521,7 @@ func (b *Executor) Prepare(ib *imagebuilder.Builder, node *parser.Node, from str
}
return errors.Wrapf(err, "error updating build context")
}
mountPoint, err := builder.Mount("")
mountPoint, err := builder.Mount(builder.MountLabel)
if err != nil {
if err2 := builder.Delete(); err2 != nil {
logrus.Debugf("error deleting container which we failed to mount: %v", err2)

View File

@@ -16,9 +16,9 @@ func importBuilderDataFromImage(store storage.Store, systemContext *types.System
imageName := ""
if imageID != "" {
ref, err := is.Transport.ParseStoreReference(store, "@"+imageID)
ref, err := is.Transport.ParseStoreReference(store, imageID)
if err != nil {
return nil, errors.Wrapf(err, "no such image %q", "@"+imageID)
return nil, errors.Wrapf(err, "no such image %q", imageID)
}
src, err2 := ref.NewImage(systemContext)
if err2 != nil {
@@ -68,7 +68,7 @@ func importBuilder(store storage.Store, options ImportOptions) (*Builder, error)
return nil, err
}
systemContext := getSystemContext(options.SignaturePolicyPath)
systemContext := getSystemContext(&types.SystemContext{}, options.SignaturePolicyPath)
builder, err := importBuilderDataFromImage(store, systemContext, c.ImageID, options.Container, c.ID)
if err != nil {
@@ -95,21 +95,27 @@ func importBuilder(store storage.Store, options ImportOptions) (*Builder, error)
}
func importBuilderFromImage(store storage.Store, options ImportFromImageOptions) (*Builder, error) {
var img *storage.Image
var err error
if options.Image == "" {
return nil, errors.Errorf("image name must be specified")
}
img, err := util.FindImage(store, options.Image)
if err != nil {
return nil, errors.Wrapf(err, "error locating image %q for importing settings", options.Image)
systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath)
for _, image := range util.ResolveName(options.Image, "", systemContext, store) {
img, err = util.FindImage(store, image)
if err != nil {
continue
}
builder, err2 := importBuilderDataFromImage(store, systemContext, img.ID, "", "")
if err2 != nil {
return nil, errors.Wrapf(err2, "error importing build settings from image %q", options.Image)
}
return builder, nil
}
systemContext := getSystemContext(options.SignaturePolicyPath)
builder, err := importBuilderDataFromImage(store, systemContext, img.ID, "", "")
if err != nil {
return nil, errors.Wrapf(err, "error importing build settings from image %q", options.Image)
}
return builder, nil
return nil, errors.Wrapf(err, "error locating image %q for importing settings", options.Image)
}

120
install.md Normal file
View File

@@ -0,0 +1,120 @@
# Installation Instructions
Prior to installing Buildah, install the following packages on your linux distro:
* make
* golang (Requires version 1.8.1 or higher.)
* bats
* btrfs-progs-devel
* bzip2
* device-mapper-devel
* git
* go-md2man
* gpgme-devel
* glib2-devel
* libassuan-devel
* ostree-devel
* runc (Requires version 1.0 RC4 or higher.)
* skopeo-containers
## Fedora
In Fedora, you can use this command:
```
dnf -y install \
make \
golang \
bats \
btrfs-progs-devel \
device-mapper-devel \
glib2-devel \
gpgme-devel \
libassuan-devel \
ostree-devel \
git \
bzip2 \
go-md2man \
runc \
skopeo-containers
```
Then to install Buildah on Fedora follow the steps in this example:
```
mkdir ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/projectatomic/buildah ./src/github.com/projectatomic/buildah
cd ./src/github.com/projectatomic/buildah
make
sudo make install
buildah --help
```
## RHEL, CentOS
In RHEL and CentOS 7, ensure that you are subscribed to `rhel-7-server-rpms`,
`rhel-7-server-extras-rpms`, and `rhel-7-server-optional-rpms`, then
run this command:
```
yum -y install \
make \
golang \
bats \
btrfs-progs-devel \
device-mapper-devel \
glib2-devel \
gpgme-devel \
libassuan-devel \
ostree-devel \
git \
bzip2 \
go-md2man \
runc \
skopeo-containers
```
The build steps for Buildah on RHEL or CentOS are the same as Fedora, above.
## Ubuntu
In Ubuntu zesty and xenial, you can use this command:
```
apt-get -y install software-properties-common
add-apt-repository -y ppa:alexlarsson/flatpak
add-apt-repository -y ppa:gophers/archive
apt-add-repository -y ppa:projectatomic/ppa
apt-get -y -qq update
apt-get -y install bats btrfs-tools git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libostree-dev libseccomp-dev libselinux1-dev skopeo-containers go-md2man
apt-get -y install golang-1.8
```
Then to install Buildah on Ubuntu follow the steps in this example:
```
mkdir ~/buildah
cd ~/buildah
export GOPATH=`pwd`
git clone https://github.com/projectatomic/buildah ./src/github.com/projectatomic/buildah
cd ./src/github.com/projectatomic/buildah
PATH=/usr/lib/go-1.8/bin:$PATH make runc all TAGS="apparmor seccomp"
sudo make install install.runc
buildah --help
```
## Debian
To install the required dependencies, you can use those commands, tested under Debian GNU/Linux amd64 9.3 (stretch):
```
gpg --recv-keys 0x018BA5AD9DF57A4448F0E6CF8BECF1637AD8C79D
gpg --export 0x018BA5AD9DF57A4448F0E6CF8BECF1637AD8C79D >> /usr/share/keyrings/projectatomic-ppa.gpg
echo 'deb [signed-by=/usr/share/keyrings/projectatomic-ppa.gpg] http://ppa.launchpad.net/projectatomic/ppa/ubuntu zesty main' > /etc/apt/sources.list.d/projectatomic-ppa.list
apt update
apt -y install -t stretch-backports libostree-dev golang
apt -y install bats btrfs-tools git libapparmor-dev libdevmapper-dev libglib2.0-dev libgpgme11-dev libseccomp-dev libselinux1-dev skopeo-containers go-md2man
```
The build steps on Debian are otherwise the same as Ubuntu, above.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 88 KiB

193
new.go
View File

@@ -2,6 +2,7 @@ package buildah
import (
"fmt"
"os"
"strings"
is "github.com/containers/image/storage"
@@ -13,6 +14,7 @@ import (
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/openshift/imagebuilder"
"github.com/pkg/errors"
"github.com/projectatomic/buildah/util"
"github.com/sirupsen/logrus"
)
@@ -25,6 +27,10 @@ const (
// can't find one in the local Store, in order to generate a source
// reference for the image that we can then copy to the local Store.
DefaultTransport = "docker://"
// minimumTruncatedIDLength is the minimum length of an identifier that
// we'll accept as possibly being a truncated image ID.
minimumTruncatedIDLength = 3
)
func reserveSELinuxLabels(store storage.Store, id string) error {
@@ -40,7 +46,9 @@ func reserveSELinuxLabels(store storage.Store, id string) error {
} else {
b, err := OpenBuilder(store, c.ID)
if err != nil {
if err == storage.ErrContainerUnknown {
if os.IsNotExist(err) {
// Ignore not exist errors since containers probably created by other tool
// TODO, we need to read other containers json data to reserve their SELinux labels
continue
}
return err
@@ -55,91 +63,150 @@ func reserveSELinuxLabels(store storage.Store, id string) error {
return nil
}
func pullAndFindImage(store storage.Store, imageName string, options BuilderOptions, sc *types.SystemContext) (*storage.Image, types.ImageReference, error) {
ref, err := pullImage(store, imageName, options, sc)
if err != nil {
logrus.Debugf("error pulling image %q: %v", imageName, err)
return nil, nil, err
}
img, err := is.Transport.GetStoreImage(store, ref)
if err != nil {
logrus.Debugf("error reading pulled image %q: %v", imageName, err)
return nil, nil, err
}
return img, ref, nil
}
func imageNamePrefix(imageName string) string {
prefix := imageName
s := strings.Split(imageName, "/")
if len(s) > 0 {
prefix = s[len(s)-1]
}
s = strings.Split(prefix, ":")
if len(s) > 0 {
prefix = s[0]
}
s = strings.Split(prefix, "@")
if len(s) > 0 {
prefix = s[0]
}
return prefix
}
func imageManifestAndConfig(ref types.ImageReference, systemContext *types.SystemContext) (manifest, config []byte, err error) {
if ref != nil {
src, err := ref.NewImage(systemContext)
if err != nil {
return nil, nil, errors.Wrapf(err, "error instantiating image for %q", transports.ImageName(ref))
}
defer src.Close()
config, err := src.ConfigBlob()
if err != nil {
return nil, nil, errors.Wrapf(err, "error reading image configuration for %q", transports.ImageName(ref))
}
manifest, _, err := src.Manifest()
if err != nil {
return nil, nil, errors.Wrapf(err, "error reading image manifest for %q", transports.ImageName(ref))
}
return manifest, config, nil
}
return nil, nil, nil
}
func newBuilder(store storage.Store, options BuilderOptions) (*Builder, error) {
var ref types.ImageReference
var img *storage.Image
manifest := []byte{}
config := []byte{}
var err error
var manifest []byte
var config []byte
if options.FromImage == BaseImageFakeName {
options.FromImage = ""
}
image := options.FromImage
if options.Transport == "" {
options.Transport = DefaultTransport
}
systemContext := getSystemContext(options.SignaturePolicyPath)
systemContext := getSystemContext(options.SystemContext, options.SignaturePolicyPath)
for _, image := range util.ResolveName(options.FromImage, options.Registry, systemContext, store) {
if len(image) >= minimumTruncatedIDLength {
if img, err = store.Image(image); err == nil && img != nil && strings.HasPrefix(img.ID, image) {
if ref, err = is.Transport.ParseStoreReference(store, img.ID); err != nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID)
}
break
}
}
imageID := ""
if image != "" {
var err error
if options.PullPolicy == PullAlways {
pulledReference, err2 := pullImage(store, options, systemContext)
pulledImg, pulledReference, err2 := pullAndFindImage(store, image, options, systemContext)
if err2 != nil {
return nil, errors.Wrapf(err2, "error pulling image %q", image)
logrus.Debugf("error pulling and reading image %q: %v", image, err2)
continue
}
ref = pulledReference
img = pulledImg
break
}
if ref == nil {
srcRef, err2 := alltransports.ParseImageName(image)
if err2 != nil {
srcRef2, err3 := alltransports.ParseImageName(options.Registry + image)
if err3 != nil {
srcRef3, err4 := alltransports.ParseImageName(options.Transport + options.Registry + image)
if err4 != nil {
return nil, errors.Wrapf(err4, "error parsing image name %q", options.Transport+options.Registry+image)
}
srcRef2 = srcRef3
}
srcRef = srcRef2
}
destImage, err2 := localImageNameForReference(store, srcRef)
if err2 != nil {
return nil, errors.Wrapf(err2, "error computing local image name for %q", transports.ImageName(srcRef))
srcRef, err2 := alltransports.ParseImageName(image)
if err2 != nil {
if options.Transport == "" {
logrus.Debugf("error parsing image name %q: %v", image, err2)
continue
}
if destImage == "" {
return nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef))
srcRef2, err3 := alltransports.ParseImageName(options.Transport + image)
if err3 != nil {
logrus.Debugf("error parsing image name %q: %v", image, err2)
continue
}
srcRef = srcRef2
}
ref, err = is.Transport.ParseStoreReference(store, destImage)
if err != nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", destImage)
}
destImage, err2 := localImageNameForReference(store, srcRef)
if err2 != nil {
return nil, errors.Wrapf(err2, "error computing local image name for %q", transports.ImageName(srcRef))
}
if destImage == "" {
return nil, errors.Errorf("error computing local image name for %q", transports.ImageName(srcRef))
}
image = destImage
ref, err = is.Transport.ParseStoreReference(store, destImage)
if err != nil {
return nil, errors.Wrapf(err, "error parsing reference to image %q", destImage)
}
img, err = is.Transport.GetStoreImage(store, ref)
if err != nil {
if errors.Cause(err) == storage.ErrImageUnknown && options.PullPolicy != PullIfMissing {
return nil, errors.Wrapf(err, "no such image %q", transports.ImageName(ref))
logrus.Debugf("no such image %q: %v", transports.ImageName(ref), err)
continue
}
ref2, err2 := pullImage(store, options, systemContext)
pulledImg, pulledReference, err2 := pullAndFindImage(store, image, options, systemContext)
if err2 != nil {
return nil, errors.Wrapf(err2, "error pulling image %q", image)
logrus.Debugf("error pulling and reading image %q: %v", image, err2)
continue
}
ref = ref2
img, err = is.Transport.GetStoreImage(store, ref)
ref = pulledReference
img = pulledImg
}
if err != nil {
return nil, errors.Wrapf(err, "no such image %q", transports.ImageName(ref))
break
}
if options.FromImage != "" && (ref == nil || img == nil) {
return nil, errors.Wrapf(storage.ErrImageUnknown, "no such image %q", options.FromImage)
}
image := options.FromImage
imageID := ""
if img != nil {
if len(img.Names) > 0 {
image = img.Names[0]
}
imageID = img.ID
src, err := ref.NewImage(systemContext)
if err != nil {
return nil, errors.Wrapf(err, "error instantiating image for %q", transports.ImageName(ref))
}
defer src.Close()
config, err = src.ConfigBlob()
if err != nil {
return nil, errors.Wrapf(err, "error reading image configuration for %q", transports.ImageName(ref))
}
manifest, _, err = src.Manifest()
if err != nil {
return nil, errors.Wrapf(err, "error reading image manifest for %q", transports.ImageName(ref))
}
}
if manifest, config, err = imageManifestAndConfig(ref, systemContext); err != nil {
return nil, errors.Wrapf(err, "error reading data from image %q", transports.ImageName(ref))
}
name := "working-container"
@@ -148,20 +215,7 @@ func newBuilder(store storage.Store, options BuilderOptions) (*Builder, error) {
} else {
var err2 error
if image != "" {
prefix := image
s := strings.Split(prefix, "/")
if len(s) > 0 {
prefix = s[len(s)-1]
}
s = strings.Split(prefix, ":")
if len(s) > 0 {
prefix = s[0]
}
s = strings.Split(prefix, "@")
if len(s) > 0 {
prefix = s[0]
}
name = prefix + "-" + name
name = imageNamePrefix(image) + "-" + name
}
suffix := 1
tmpName := name
@@ -174,6 +228,7 @@ func newBuilder(store storage.Store, options BuilderOptions) (*Builder, error) {
}
name = tmpName
}
coptions := storage.ContainerOptions{}
container, err := store.CreateContainer("", []string{name}, imageID, "", "", &coptions)
if err != nil {
@@ -188,7 +243,7 @@ func newBuilder(store storage.Store, options BuilderOptions) (*Builder, error) {
}
}()
if err := reserveSELinuxLabels(store, container.ID); err != nil {
if err = reserveSELinuxLabels(store, container.ID); err != nil {
return nil, err
}
processLabel, mountLabel, err := label.InitLabels(nil)

43
pull.go
View File

@@ -53,27 +53,17 @@ func localImageNameForReference(store storage.Store, srcRef types.ImageReference
return name, nil
}
func pullImage(store storage.Store, options BuilderOptions, sc *types.SystemContext) (types.ImageReference, error) {
name := options.FromImage
spec := name
if options.Registry != "" {
spec = options.Registry + spec
}
spec2 := spec
if options.Transport != "" {
spec2 = options.Transport + spec
}
srcRef, err := alltransports.ParseImageName(name)
func pullImage(store storage.Store, imageName string, options BuilderOptions, sc *types.SystemContext) (types.ImageReference, error) {
spec := imageName
srcRef, err := alltransports.ParseImageName(spec)
if err != nil {
if options.Transport == "" {
return nil, errors.Wrapf(err, "error parsing image name %q", spec)
}
spec = options.Transport + spec
srcRef2, err2 := alltransports.ParseImageName(spec)
if err2 != nil {
srcRef3, err3 := alltransports.ParseImageName(spec2)
if err3 != nil {
return nil, errors.Wrapf(err3, "error parsing image name %q", spec2)
}
srcRef2 = srcRef3
return nil, errors.Wrapf(err2, "error parsing image name %q", spec)
}
srcRef = srcRef2
}
@@ -91,6 +81,12 @@ func pullImage(store storage.Store, options BuilderOptions, sc *types.SystemCont
return nil, errors.Wrapf(err, "error parsing image name %q", destName)
}
img, err := srcRef.NewImageSource(sc)
if err != nil {
return nil, errors.Wrapf(err, "error initializing %q as an image source", spec)
}
img.Close()
policy, err := signature.DefaultPolicy(sc)
if err != nil {
return nil, errors.Wrapf(err, "error obtaining default signature policy")
@@ -103,12 +99,15 @@ func pullImage(store storage.Store, options BuilderOptions, sc *types.SystemCont
defer func() {
if err2 := policyContext.Destroy(); err2 != nil {
logrus.Debugf("error destroying signature polcy context: %v", err2)
logrus.Debugf("error destroying signature policy context: %v", err2)
}
}()
logrus.Debugf("copying %q to %q", spec, name)
logrus.Debugf("copying %q to %q", spec, destName)
err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, options.SystemContext, nil))
return destRef, err
err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, options.SystemContext, nil, ""))
if err == nil {
return destRef, nil
}
return nil, err
}

26
run.go
View File

@@ -12,6 +12,7 @@ import (
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
@@ -134,6 +135,9 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, optionMounts
if err = os.MkdirAll(volumePath, 0755); err != nil {
return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, volume, b.ContainerID)
}
if err = label.Relabel(volumePath, b.MountLabel, false); err != nil {
return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, volume, b.ContainerID)
}
srcPath := filepath.Join(mountPoint, volume)
if err = copyFileWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, volume, b.ContainerID, srcPath)
@@ -208,6 +212,28 @@ func (b *Builder) Run(command []string, options RunOptions) error {
logrus.Errorf("error unmounting container: %v", err2)
}
}()
for _, mp := range []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
} {
g.AddLinuxMaskedPaths(mp)
}
for _, rp := range []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
} {
g.AddLinuxReadonlyPaths(rp)
}
g.SetRootPath(mountPoint)
switch options.Terminal {
case DefaultTerminal:

28
tests/authenticate.bats Normal file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bats
load helpers
@test "from-authenticate-cert-and-creds" {
buildah from --pull --name "alpine" --signature-policy ${TESTSDIR}/policy.json alpine
run buildah push --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword alpine localhost:5000/my-alpine
echo "$output"
[ "$status" -eq 0 ]
# This should fail
run buildah push localhost:5000/my-alpine --signature-policy ${TESTSDIR}/policy.json --tls-verify=true
[ "$status" -ne 0 ]
# This should fail
run buildah from localhost:5000/my-alpine --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds baduser:badpassword
[ "$status" -ne 0 ]
# This should work
run buildah from localhost:5000/my-alpine --name "my-alpine" --signature-policy ${TESTSDIR}/policy.json --tls-verify=false --creds testuser:testpassword
[ "$status" -eq 0 ]
# Clean up
buildah rm my-alpine
buildah rm alpine
buildah rmi -f $(buildah --debug=false images -q)
}

View File

@@ -110,5 +110,6 @@ load helpers
buildah rmi $id
done
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" == "" ]
}

View File

@@ -9,6 +9,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -24,6 +25,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
target=alpine-image
@@ -37,6 +39,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -52,6 +55,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
target=alpine-image
@@ -65,6 +69,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -88,6 +93,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -100,6 +106,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -112,6 +119,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -124,6 +132,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -136,6 +145,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -155,6 +165,7 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -168,6 +179,7 @@ load helpers
buildah --debug=false images -q
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -177,6 +189,7 @@ load helpers
target3=so-many-scratch-images
buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -t ${target2} -t ${target3} ${TESTSDIR}/bud/from-scratch
run buildah --debug=false images
[ "$status" -eq 0 ]
cid=$(buildah from ${target})
buildah rm ${cid}
cid=$(buildah from library/${target2})
@@ -185,6 +198,7 @@ load helpers
buildah rm ${cid}
buildah rmi -f $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -200,10 +214,12 @@ load helpers
run test -s $root/vol/subvol/subvolfile
[ "$status" -ne 0 ]
run stat -c %f $root/vol/subvol
[ "$status" -eq 0 ]
[ "$output" = 41ed ]
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}
@@ -217,5 +233,6 @@ load helpers
buildah rm ${cid}
buildah rmi $(buildah --debug=false images -q)
run buildah --debug=false images -q
[ "$status" -eq 0 ]
[ "$output" = "" ]
}

124
tests/byid.bats Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env bats
load helpers
@test "from-by-id" {
image=busybox
# Pull down the image, if we have to.
cid=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $image)
[ $? -eq 0 ]
[ $(wc -l <<< "$cid") -eq 1 ]
buildah rm $cid
# Get the image's ID.
run buildah --debug=false images -q $image
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
iid="$output"
# Use the image's ID to create a container.
run buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json ${iid}
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
cid="$output"
buildah rm $cid
# Use a truncated form of the image's ID to create a container.
run buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json ${iid:0:6}
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
cid="$output"
buildah rm $cid
buildah rmi $iid
}
@test "inspect-by-id" {
image=busybox
# Pull down the image, if we have to.
cid=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $image)
[ $? -eq 0 ]
[ $(wc -l <<< "$cid") -eq 1 ]
buildah rm $cid
# Get the image's ID.
run buildah --debug=false images -q $image
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
iid="$output"
# Use the image's ID to inspect it.
run buildah --debug=false inspect --type=image ${iid}
echo "$output"
[ $status -eq 0 ]
# Use a truncated copy of the image's ID to inspect it.
run buildah --debug=false inspect --type=image ${iid:0:6}
echo "$output"
[ $status -eq 0 ]
buildah rmi $iid
}
@test "push-by-id" {
for image in busybox kubernetes/pause ; do
echo pulling/pushing image $image
TARGET=${TESTDIR}/subdir-$(basename $image)
mkdir -p $TARGET $TARGET-truncated
# Pull down the image, if we have to.
cid=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $image)
[ $? -eq 0 ]
[ $(wc -l <<< "$cid") -eq 1 ]
buildah rm $cid
# Get the image's ID.
run buildah --debug=false images -q $IMAGE
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
iid="$output"
# Use the image's ID to push it.
run buildah push --signature-policy ${TESTSDIR}/policy.json $iid dir:$TARGET
echo "$output"
[ $status -eq 0 ]
# Use a truncated form of the image's ID to push it.
run buildah push --signature-policy ${TESTSDIR}/policy.json ${iid:0:6} dir:$TARGET-truncated
echo "$output"
[ $status -eq 0 ]
# Use the image's complete ID to remove it.
buildah rmi $iid
done
}
@test "rmi-by-id" {
image=busybox
# Pull down the image, if we have to.
cid=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $image)
[ $? -eq 0 ]
[ $(wc -l <<< "$cid") -eq 1 ]
buildah rm $cid
# Get the image's ID.
run buildah --debug=false images -q $image
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
iid="$output"
# Use a truncated copy of the image's ID to remove it.
run buildah --debug=false rmi ${iid:0:6}
echo "$output"
[ $status -eq 0 ]
}

View File

@@ -111,3 +111,23 @@ load helpers
[ "$status" -ne 0 ]
buildah rm $cid
}
@test "copy --chown" {
mkdir -p ${TESTDIR}/subdir
createrandom ${TESTDIR}/randomfile
createrandom ${TESTDIR}/subdir/randomfile
createrandom ${TESTDIR}/subdir/other-randomfile
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
root=$(buildah mount $cid)
buildah config --workingdir / $cid
buildah copy --chown 1:1 $cid ${TESTDIR}/randomfile
buildah copy --chown root:1 $cid ${TESTDIR}/randomfile /randomfile2
buildah copy --chown nobody $cid ${TESTDIR}/randomfile /randomfile3
buildah copy --chown nobody:root $cid ${TESTDIR}/subdir /subdir
test $(stat -c "%u:%g" $root/randomfile) = "1:1"
test $(stat -c "%U:%g" $root/randomfile2) = "root:1"
test $(stat -c "%U" $root/randomfile3) = "nobody"
(cd $root/subdir/; for i in *; do test $(stat -c "%U:%G" $i) = "nobody:root"; done)
buildah rm $cid
}

40
tests/digest.bats Normal file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bats
load helpers
fromreftest() {
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json $1)
pushdir=${TESTDIR}/fromreftest
mkdir -p ${pushdir}/{1,2,3}
buildah push --signature-policy ${TESTSDIR}/policy.json $1 dir:${pushdir}/1
buildah commit --signature-policy ${TESTSDIR}/policy.json $cid new-image
buildah push --signature-policy ${TESTSDIR}/policy.json new-image dir:${pushdir}/2
buildah rmi new-image
buildah commit --signature-policy ${TESTSDIR}/policy.json $cid dir:${pushdir}/3
buildah rm $cid
rm -fr ${pushdir}
}
@test "from-by-digest-s1" {
fromreftest kubernetes/pause@sha256:f8cd50c5a287dd8c5f226cf69c60c737d34ed43726c14b8a746d9de2d23eda2b
}
@test "from-by-tag-s1" {
fromreftest kubernetes/pause:go
}
@test "from-by-repo-only-s1" {
fromreftest kubernetes/pause
}
@test "from-by-digest-s2" {
fromreftest alpine@sha256:e9cec9aec697d8b9d450edd32860ecd363f2f3174c8338beb5f809422d182c63
}
@test "from-by-tag-s2" {
fromreftest alpine:2.6
}
@test "from-by-repo-only-s2" {
fromreftest alpine
}

View File

@@ -42,7 +42,7 @@ function createrandom() {
}
function buildah() {
${BUILDAH_BINARY} --debug --root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER} "$@"
${BUILDAH_BINARY} --debug --registries-conf ${TESTSDIR}/registries.conf --root ${TESTDIR}/root --runroot ${TESTDIR}/runroot --storage-driver ${STORAGE_DRIVER} "$@"
}
function imgtype() {

View File

@@ -8,6 +8,7 @@ import (
"strings"
is "github.com/containers/image/storage"
"github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/opencontainers/image-spec/specs-go/v1"
@@ -33,7 +34,9 @@ func main() {
policy := flag.String("signature-policy", "", "signature policy file")
mtype := flag.String("expected-manifest-type", buildah.OCIv1ImageManifest, "expected manifest type")
showm := flag.Bool("show-manifest", false, "output the manifest JSON")
rebuildm := flag.Bool("rebuild-manifest", false, "rebuild the manifest JSON")
showc := flag.Bool("show-config", false, "output the configuration JSON")
rebuildc := flag.Bool("rebuild-config", false, "rebuild the configuration JSON")
flag.Parse()
logrus.SetLevel(logrus.ErrorLevel)
if debug != nil && *debug {
@@ -79,6 +82,7 @@ func main() {
logrus.Errorf("error opening storage: %v", err)
return
}
is.Transport.SetStore(store)
errors := false
defer func() {
@@ -88,6 +92,7 @@ func main() {
}
}()
for _, image := range args {
var ref types.ImageReference
oImage := v1.Image{}
dImage := docker.V2Image{}
oManifest := v1.Manifest{}
@@ -97,9 +102,13 @@ func main() {
ref, err := is.Transport.ParseStoreReference(store, image)
if err != nil {
logrus.Errorf("error parsing reference %q: %v", image, err)
errors = true
continue
ref2, err2 := alltransports.ParseImageName(image)
if err2 != nil {
logrus.Errorf("error parsing reference %q: %v", image, err)
errors = true
continue
}
ref = ref2
}
img, err := ref.NewImage(systemContext)
@@ -161,6 +170,66 @@ func main() {
errors = true
continue
}
switch manifestType {
case buildah.OCIv1ImageManifest:
if rebuildm != nil && *rebuildm {
err = json.Unmarshal(manifest, &oManifest)
if err != nil {
logrus.Errorf("error parsing manifest from %q: %v", image, err)
errors = true
continue
}
manifest, err = json.Marshal(oManifest)
if err != nil {
logrus.Errorf("error rebuilding manifest from %q: %v", image, err)
errors = true
continue
}
}
if rebuildc != nil && *rebuildc {
err = json.Unmarshal(config, &oImage)
if err != nil {
logrus.Errorf("error parsing config from %q: %v", image, err)
errors = true
continue
}
config, err = json.Marshal(oImage)
if err != nil {
logrus.Errorf("error rebuilding config from %q: %v", image, err)
errors = true
continue
}
}
case buildah.Dockerv2ImageManifest:
if rebuildm != nil && *rebuildm {
err = json.Unmarshal(manifest, &dManifest)
if err != nil {
logrus.Errorf("error parsing manifest from %q: %v", image, err)
errors = true
continue
}
manifest, err = json.Marshal(dManifest)
if err != nil {
logrus.Errorf("error rebuilding manifest from %q: %v", image, err)
errors = true
continue
}
}
if rebuildc != nil && *rebuildc {
err = json.Unmarshal(config, &dImage)
if err != nil {
logrus.Errorf("error parsing config from %q: %v", image, err)
errors = true
continue
}
config, err = json.Marshal(dImage)
if err != nil {
logrus.Errorf("error rebuilding config from %q: %v", image, err)
errors = true
continue
}
}
}
if expectedConfigType != "" && configType != expectedConfigType {
logrus.Errorf("expected config type %q in %q, got %q", expectedConfigType, image, configType)
errors = true

View File

@@ -8,13 +8,48 @@ load helpers
cid=$(buildah from --pull=false --signature-policy ${TESTSDIR}/policy.json ${source})
for format in "" docker oci ; do
mkdir -p ${TESTDIR}/committed${format:+.${format}}
buildah commit ${format:+--format ${format}} --reference-time ${TESTDIR}/reference-time-file --signature-policy ${TESTSDIR}/policy.json "$cid" scratch-image${format:+-${format}}
buildah commit ${format:+--format ${format}} --reference-time ${TESTDIR}/reference-time-file --signature-policy ${TESTSDIR}/policy.json "$cid" dir:${TESTDIR}/committed${format:+.${format}}
# Force no compression to generate what we push.
buildah commit -D ${format:+--format ${format}} --reference-time ${TESTDIR}/reference-time-file --signature-policy ${TESTSDIR}/policy.json "$cid" scratch-image${format:+-${format}}
buildah commit -D ${format:+--format ${format}} --reference-time ${TESTDIR}/reference-time-file --signature-policy ${TESTSDIR}/policy.json "$cid" dir:${TESTDIR}/committed${format:+.${format}}
mkdir -p ${TESTDIR}/pushed${format:+.${format}}
buildah push --signature-policy ${TESTSDIR}/policy.json scratch-image${format:+-${format}} dir:${TESTDIR}/pushed${format:+.${format}}
diff -u ${TESTDIR}/committed${format:+.${format}}/manifest.json ${TESTDIR}/pushed${format:+.${format}}/manifest.json
# Reencode the manifest to lose variations due to different encoders or definitions of structures.
imgtype -expected-manifest-type "*" -rebuild-manifest -show-manifest dir:${TESTDIR}/committed${format:+.${format}} > ${TESTDIR}/manifest.committed${format:+.${format}}
imgtype -expected-manifest-type "*" -rebuild-manifest -show-manifest dir:${TESTDIR}/pushed${format:+.${format}} > ${TESTDIR}/manifest.pushed${format:+.${format}}
diff -u ${TESTDIR}/manifest.committed${format:+.${format}} ${TESTDIR}/manifest.pushed${format:+.${format}}
[ "$output" = "" ]
done
buildah rm "$cid"
done
}
@test "push with manifest type conversion" {
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah push --signature-policy ${TESTSDIR}/policy.json --format oci alpine dir:my-dir
echo "$output"
[ "$status" -eq 0 ]
manifest=$(cat my-dir/manifest.json)
run grep "application/vnd.oci.image.config.v1+json" <<< "$manifest"
echo "$output"
[ "$status" -eq 0 ]
run buildah push --signature-policy ${TESTSDIR}/policy.json --format v2s2 alpine dir:my-dir
echo "$output"
[ "$status" -eq 0 ]
run grep "application/vnd.docker.distribution.manifest.v2+json" my-dir/manifest.json
echo "$output"
[ "$status" -eq 0 ]
buildah rm "$cid"
buildah rmi alpine
rm -rf my-dir
}
@test "push with imageid" {
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
imageid=$(buildah images -q)
run buildah push --signature-policy ${TESTSDIR}/policy.json $imageid dir:my-dir
echo "$output"
[ "$status" -eq 0 ]
buildah rm "$cid"
buildah rmi alpine
rm -rf my-dir
}

57
tests/registries.bats Normal file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env bats
load helpers
@test "registries" {
registrypair() {
image=$1
imagename=$2
# Clean up.
for id in $(buildah --debug=false containers -q) ; do
buildah rm ${id}
done
for id in $(buildah --debug=false images -q) ; do
buildah rmi ${id}
done
# Create a container by specifying the image with one name.
buildah from --pull --signature-policy ${TESTSDIR}/policy.json $image
# Create a container by specifying the image with another name.
buildah from --pull --signature-policy ${TESTSDIR}/policy.json $imagename
# Get their image IDs. They should be the same one.
lastid=
for cid in $(buildah --debug=false containers -q) ; do
run buildah --debug=false inspect -f "{{.FromImageID}}" $cid
echo "$output"
[ $status -eq 0 ]
[ $(wc -l <<< "$output") -eq 1 ]
if [ "$lastid" != "" ] ; then
[ "$output" = "$lastid" ]
fi
lastid="$output"
done
# A quick bit of troubleshooting help.
run buildah images
echo "$output"
[ "$iid" = "$nameiid" ]
# Clean up.
for id in $(buildah --debug=false containers -q) ; do
buildah rm ${id}
done
for id in $(buildah --debug=false images -q) ; do
buildah rmi ${id}
done
}
# Test with pairs of short and fully-qualified names that should be the same image.
registrypair busybox docker.io/busybox
registrypair docker.io/busybox busybox
registrypair busybox docker.io/library/busybox
registrypair docker.io/library/busybox busybox
registrypair fedora-minimal registry.fedoraproject.org/fedora-minimal
registrypair registry.fedoraproject.org/fedora-minimal fedora-minimal
}

25
tests/registries.conf Normal file
View File

@@ -0,0 +1,25 @@
# This is a system-wide configuration file used to
# keep track of registries for various container backends.
# It adheres to TOML format and does not support recursive
# lists of registries.
# The default location for this configuration file is /etc/containers/registries.conf.
# The only valid categories are: 'registries.search', 'registries.insecure',
# and 'registries.block'.
[registries.search]
registries = ['docker.io', 'registry.fedoraproject.org', 'registry.access.redhat.com']
# If you need to access insecure registries, add the registry's fully-qualified name.
# An insecure registry is one that does not have a valid SSL certificate or only does HTTP.
[registries.insecure]
registries = []
# If you need to block pull access from a registry, uncomment the section below
# and add the registries fully-qualified name.
#
# Docker only
[registries.block]
registries = []

View File

@@ -8,7 +8,7 @@ load helpers
fi
# Build a container to use for building the binaries.
image=registry.fedoraproject.org/fedora:26
image=registry.fedoraproject.org/fedora:27
cid=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $image)
root=$(buildah --debug=false mount $cid)
commit=$(git log --format=%H -n 1)

View File

@@ -74,26 +74,32 @@ load helpers
buildah config $cid --entrypoint ""
buildah config $cid --cmd pwd
run buildah --debug=false run $cid
[ "$status" -eq 0 ]
[ "$output" = /tmp ]
buildah config $cid --entrypoint echo
run buildah --debug=false run $cid
[ "$status" -eq 0 ]
[ "$output" = pwd ]
buildah config $cid --cmd ""
run buildah --debug=false run $cid
[ "$status" -eq 0 ]
[ "$output" = "" ]
buildah config $cid --entrypoint ""
run buildah --debug=false run $cid echo that-other-thing
[ "$status" -eq 0 ]
[ "$output" = that-other-thing ]
buildah config $cid --cmd echo
run buildah --debug=false run $cid echo that-other-thing
[ "$status" -eq 0 ]
[ "$output" = that-other-thing ]
buildah config $cid --entrypoint echo
run buildah --debug=false run $cid echo that-other-thing
[ "$status" -eq 0 ]
[ "$output" = that-other-thing ]
buildah rm $cid
@@ -112,8 +118,10 @@ load helpers
root=$(buildah mount $cid)
testuser=jimbo
testbogususer=nosuchuser
testgroup=jimbogroup
testuid=$RANDOM
testotheruid=$RANDOM
testgid=$RANDOM
testgroupid=$RANDOM
echo "$testuser:x:$testuid:$testgid:Jimbo Jenkins:/home/$testuser:/bin/sh" >> $root/etc/passwd
@@ -122,52 +130,99 @@ load helpers
buildah config $cid -u ""
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = 0 ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = 0 ]
buildah config $cid -u ${testuser}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgid ]
buildah config $cid -u ${testuid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgid ]
buildah config $cid -u ${testuser}:${testgroup}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgroupid ]
buildah config $cid -u ${testuid}:${testgroup}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgroupid ]
buildah config $cid -u ${testotheruid}:${testgroup}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testotheruid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgroupid ]
buildah config $cid -u ${testotheruid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testotheruid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = 0 ]
buildah config $cid -u ${testuser}:${testgroupid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgroupid ]
buildah config $cid -u ${testuid}:${testgroupid}
buildah run -- $cid id
run buildah --debug=false run -- $cid id -u
[ "$status" -eq 0 ]
[ "$output" = $testuid ]
run buildah --debug=false run -- $cid id -g
[ "$status" -eq 0 ]
[ "$output" = $testgroupid ]
buildah config $cid -u ${testbogususer}
run buildah --debug=false run -- $cid id -u
[ "$status" -ne 0 ]
[[ "$output" =~ "unknown user" ]]
run buildah --debug=false run -- $cid id -g
[ "$status" -ne 0 ]
[[ "$output" =~ "unknown user" ]]
ln -vsf /etc/passwd $root/etc/passwd
buildah config $cid -u ${testuser}:${testgroup}
run buildah --debug=false run -- $cid id -u
echo "$output"
[ "$status" -ne 0 ]
[[ "$output" =~ "unknown user" ]]
buildah unmount $cid
buildah rm $cid
}
@@ -177,17 +232,14 @@ load helpers
skip
fi
runc --version
createrandom ${TESTDIR}/randomfile
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json fedora)
root=$(buildah mount $cid)
run buildah --debug=false run -- $cid dnf -y install hostname
[ "$status" -eq 0 ]
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
run buildah --debug=false run $cid hostname
echo "$output"
[ "$status" -eq 0 ]
[ "$output" != "foobar" ]
run buildah --debug=false run --hostname foobar $cid hostname
echo "$output"
[ "$status" -eq 0 ]
[ "$output" = "foobar" ]
buildah unmount $cid
buildah rm $cid
}

View File

@@ -1,10 +1,18 @@
#!/bin/bash
# test_buildah_authentication
# A script to be run at the command line with Buildah installed.
# This currently needs to be run as root and Docker must be
# installed on the system.
# This will test the code and should be run with this command:
#
# /bin/bash -v test_buildah_authentication.sh
########
# System setup - Create dir for creds and start Docker
########
mkdir -p /root/auth
systemctl restart docker
########
# Create creds and store in /root/auth/htpasswd
########

View File

@@ -0,0 +1,193 @@
#!/bin/bash
# test_buildah_baseline.sh
# A script to be run at the command line with Buildah installed.
# This should be run against a new kit to provide base level testing
# on a freshly installed machine with no images or containers in
# play. This currently needs to be run as root.
#
# Commands based on the tutorial provided by William Henry.
#
# To run this command:
#
# /bin/bash -v test_buildah_baseline.sh
########
# Next two commands should return blanks
########
buildah images
buildah containers
########
# Create Fedora based container
########
container=$(buildah from fedora)
echo $container
########
# Run container and display contents in /etc
########
buildah run $container -- ls -alF /etc
########
# Run Java in the container - should FAIL
########
buildah run $container java
########
# Install java onto the container
########
buildah run $container -- dnf -y install java
########
# Run Java in the container - should show java usage
########
buildah run $container java
########
# Create a scratch container
########
newcontainer=$(buildah from scratch)
########
# Check and find two containers
########
buildah containers
########
# Check images, no "scratch" image
########
buildah images
########
# Run the container - should FAIL
########
buildah run $newcontainer bash
########
# Mount the container's root file system
########
scratchmnt=$(buildah mount $newcontainer)
########
# Show the location, should be /var/lib/containers/storage/overlay/{id}/dif
########
echo $scratchmnt
########
# Install Fedora 27 bash and coreutils
########
dnf install --installroot $scratchmnt --release 27 bash coreutils --setopt install_weak_deps=false -y
########
# Check /usr/bin on the new container
########
buildah run $newcontainer -- ls -alF /usr/bin
########
# Create shell script to test on
########
FILE=./runecho.sh
/bin/cat <<EOM >$FILE
#!/bin/bash
for i in {1..9};
do
echo "This is a new container from ipbabble [" \$i "]"
done
EOM
chmod +x $FILE
########
# Copy and run file on scratch container
########
buildah copy $newcontainer $FILE /usr/bin
buildah config --cmd /usr/bin/runecho.sh $newcontainer
buildah run $newcontainer
########
# Add configuration information
########
buildah config --created-by "ipbabble" $newcontainer
buildah config --author "wgh at redhat.com @ipbabble" --label name=fedora27-bashecho $newcontainer
########
# Inspect the container, verifying above was put into it
########
buildah inspect $newcontainer
########
# Unmount the container
########
buildah unmount $newcontainer
########
# Commit the image
########
buildah commit $newcontainer fedora-bashecho
########
# Check the images there should be a fedora-bashecho:latest image
########
buildah images
########
# Inspect the fedora-bashecho image
########
buildah inspect --type=image fedora-bashecho
########
# Remove the container
########
buildah rm $newcontainer
########
# Install Docker, but not for long!
########
dnf -y install docker
systemctl start docker
########
# Push fedora-bashecho to the Docker daemon
########
buildah push fedora-bashecho docker-daemon:fedora-bashecho:latest
########
# Run fedora-bashecho from Docker
########
docker run fedora-bashecho
########
# Time to remove Docker
########
dnf -y remove docker
########
# Build Dockerfile
########
FILE=./Dockerfile
/bin/cat <<EOM >$FILE
FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay
EOM
chmod +x $FILE
########
# Build with the Dockerfile
########
buildah bud -f Dockerfile -t whale-says
########
# Create a whalesays container
########
whalesays=$(buildah from whale-says)
########
# Run the container to see what the whale says
########
buildah run $whalesays
########
# Clean up Buildah
########
buildah rm $(buildah containers -q)
buildah rmi -f $(buildah --debug=false images -q)

124
tests/test_buildah_build_rpm.sh Executable file
View File

@@ -0,0 +1,124 @@
#!/bin/bash
#
# test_buildah_build_rpm.sh
#
# Meant to run on a freshly installed VM.
# Installs the latest Git and Buildah and then
# Builds and installs Buildah's RPM in a Buidah Container.
# The baseline test is then run on this vm and then the
# newly created BUILDAH rpm is installed and the baseline
# test is rerun.
#
########
# Setup
########
IMAGE=registry.fedoraproject.org/fedora
SBOX=/tmp/sandbox
PACKAGES=/tmp/packages
mkdir -p ${SBOX}/buildah
GITROOT=${SBOX}/buildah
TESTSDIR=${GITROOT}/tests
# Change packager as appropriate for the platform
PACKAGER=dnf
${PACKAGER} install -y git
${PACKAGER} install -y buildah
########
# Clone buildah from GitHub.com
########
cd $SBOX
git clone https://github.com/projectatomic/buildah.git
cd $GITROOT
########
# Build a container to use for building the binaries.
########
CTRID=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $IMAGE)
ROOTMNT=$(buildah --debug=false mount $CTRID)
COMMIT=$(git log --format=%H -n 1)
SHORTCOMMIT=$(echo ${COMMIT} | cut -c-7)
mkdir -p ${ROOTMNT}/rpmbuild/{SOURCES,SPECS}
########
# Build the tarball.
########
(git archive --format tar.gz --prefix=buildah-${COMMIT}/ ${COMMIT}) > ${ROOTMNT}/rpmbuild/SOURCES/buildah-${SHORTCOMMIT}.tar.gz
########
# Update the .spec file with the commit ID.
########
sed s:REPLACEWITHCOMMITID:${COMMIT}:g ${GITROOT}/contrib/rpm/buildah.spec > ${ROOTMNT}/rpmbuild/SPECS/buildah.spec
########
# Install build dependencies and build binary packages.
########
buildah --debug=false run $CTRID -- dnf -y install 'dnf-command(builddep)' rpm-build
buildah --debug=false run $CTRID -- dnf -y builddep --spec /rpmbuild/SPECS/buildah.spec
buildah --debug=false run $CTRID -- rpmbuild --define "_topdir /rpmbuild" -ba /rpmbuild/SPECS/buildah.spec
########
# Build a second new container.
########
CTRID2=$(buildah --debug=false from --pull --signature-policy ${TESTSDIR}/policy.json $IMAGE)
ROOTMNT2=$(buildah --debug=false mount $CTRID2)
########
# Copy the binary packages from the first container to the second one and to
# /tmp. Also build a list of their filenames.
########
rpms=
mkdir -p ${ROOTMNT2}/${PACKAGES}
mkdir -p ${PACKAGES}
for rpm in ${ROOTMNT}/rpmbuild/RPMS/*/*.rpm ; do
cp $rpm ${ROOTMNT2}/${PACKAGES}
cp $rpm ${PACKAGES}
rpms="$rpms "${PACKAGES}/$(basename $rpm)
done
########
# Install the binary packages into the second container.
########
buildah --debug=false run $CTRID2 -- dnf -y install $rpms
########
# Run the binary package and compare its self-identified version to the one we tried to build.
########
id=$(buildah --debug=false run $CTRID2 -- buildah version | awk '/^Git Commit:/ { print $NF }')
bv=$(buildah --debug=false run $CTRID2 -- buildah version | awk '/^Version:/ { print $NF }')
rv=$(buildah --debug=false run $CTRID2 -- rpm -q --queryformat '%{version}' buildah)
echo "short commit: $SHORTCOMMIT"
echo "id: $id"
echo "buildah version: $bv"
echo "buildah rpm version: $rv"
test $SHORTCOMMIT = $id
test $bv = $rv
########
# Clean up Buildah
########
buildah rm $(buildah containers -q)
buildah rmi -f $(buildah --debug=false images -q)
########
# Kick off baseline testing against the installed Buildah
########
/bin/bash -v ${TESTSDIR}/test_buildah_baseline.sh
########
# Install the Buildah we just built locally and run
# the baseline tests again.
########
${PACKAGER} -y install ${PACKAGES}/*.rpm
/bin/bash -v ${TESTSDIR}/test_buildah_baseline.sh
########
# Clean up
########
rm -rf ${SBOX}
rm -rf ${PACKAGES}
buildah rm $(buildah containers -q)
buildah rmi -f $(buildah images -q)
${PACKAGER} remove -y buildah

View File

@@ -3,6 +3,10 @@ set -e
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
# Default to using /var/tmp for test space, since it's more likely to support
# labels than /tmp, which is often on tmpfs.
export TMPDIR=${TMPDIR:-/var/tmp}
# Load the helpers.
. helpers.bash

View File

@@ -8,6 +8,6 @@ if ! which git-validation > /dev/null 2> /dev/null ; then
fi
if test "$TRAVIS" != true ; then
#GITVALIDATE_EPOCH=":/git-validation epoch"
GITVALIDATE_EPOCH="b1bb73e01c9bf0b1b75e50a2d1947b14a8174eee"
GITVALIDATE_EPOCH="bf40000e72b351067ebae7b77d212a200f9ce051"
fi
exec git-validation -q -run DCO,short-subject ${GITVALIDATE_EPOCH:+-range "${GITVALIDATE_EPOCH}""..${GITVALIDATE_TIP:-@}"} ${GITVALIDATE_FLAGS}

View File

@@ -10,6 +10,7 @@ load helpers
@test "buildah version up to date in .spec file" {
run buildah version
[ "$status" -eq 0 ]
bversion=$(echo "$output" | awk '/^Version:/ { print $NF }')
rversion=$(cat ${TESTSDIR}/../contrib/rpm/buildah.spec | awk '/^Version:/ { print $NF }')
test "$bversion" = "$rversion"

27
user.go
View File

@@ -26,39 +26,34 @@ func getUser(rootdir, userspec string) (specs.User, error) {
uid64, uerr := strconv.ParseUint(userspec, 10, 32)
if uerr == nil && groupspec == "" {
// We parsed the user name as a number, and there's no group
// component, so we need to look up the user's primary GID.
// component, so try to look up the primary GID of the user who
// has this UID.
var name string
name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
if gerr == nil {
userspec = name
} else {
if userrec, err := user.LookupId(userspec); err == nil {
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
userspec = userrec.Name
}
// Leave userspec alone, but swallow the error and just
// use GID 0.
gid64 = 0
gerr = nil
}
}
if uerr != nil {
// The user ID couldn't be parsed as a number, so try to look
// up the user's UID and primary GID.
uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
gerr = uerr
}
if uerr != nil {
if userrec, err := user.Lookup(userspec); err == nil {
uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
}
}
if groupspec != "" {
// We have a group name or number, so parse it.
gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
if gerr != nil {
// The group couldn't be parsed as a number, so look up
// the group's GID.
gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
}
if gerr != nil {
if group, err := user.LookupGroup(groupspec); err == nil {
gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
}
}
}
if uerr == nil && gerr == nil {

View File

@@ -1,4 +1,4 @@
// +build !cgo !linux
// +build !linux
package buildah

235
user_linux.go Normal file
View File

@@ -0,0 +1,235 @@
// +build linux
package buildah
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"strconv"
"strings"
"sync"
"github.com/containers/storage/pkg/reexec"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
const (
openChrootedCommand = Package + "-open"
)
func init() {
reexec.Register(openChrootedCommand, openChrootedFileMain)
}
func openChrootedFileMain() {
status := 0
flag.Parse()
if len(flag.Args()) < 1 {
os.Exit(1)
}
// Our first parameter is the directory to chroot into.
if err := unix.Chdir(flag.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "chdir(): %v", err)
os.Exit(1)
}
if err := unix.Chroot(flag.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "chroot(): %v", err)
os.Exit(1)
}
// Anything else is a file we want to dump out.
for _, filename := range flag.Args()[1:] {
f, err := os.Open(filename)
if err != nil {
fmt.Fprintf(os.Stderr, "open(%q): %v", filename, err)
status = 1
continue
}
_, err = io.Copy(os.Stdout, f)
if err != nil {
fmt.Fprintf(os.Stderr, "read(%q): %v", filename, err)
}
f.Close()
}
os.Exit(status)
}
func openChrootedFile(rootdir, filename string) (*exec.Cmd, io.ReadCloser, error) {
// The child process expects a chroot and one or more filenames that
// will be consulted relative to the chroot directory and concatenated
// to its stdout. Start it up.
cmd := reexec.Command(openChrootedCommand, rootdir, filename)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, nil, err
}
err = cmd.Start()
if err != nil {
return nil, nil, err
}
// Hand back the child's stdout for reading, and the child to reap.
return cmd, stdout, nil
}
var (
lookupUser, lookupGroup sync.Mutex
)
type lookupPasswdEntry struct {
name string
uid uint64
gid uint64
}
type lookupGroupEntry struct {
name string
gid uint64
}
func readWholeLine(rc *bufio.Reader) ([]byte, error) {
line, isPrefix, err := rc.ReadLine()
if err != nil {
return nil, err
}
for isPrefix {
// We didn't get a whole line. Keep reading chunks until we find an end of line, and discard them.
for isPrefix {
logrus.Debugf("discarding partial line %q", string(line))
_, isPrefix, err = rc.ReadLine()
if err != nil {
return nil, err
}
}
// That last read was the end of a line, so now we try to read the (beginning of?) the next line.
line, isPrefix, err = rc.ReadLine()
if err != nil {
return nil, err
}
}
return line, nil
}
func parseNextPasswd(rc *bufio.Reader) *lookupPasswdEntry {
line, err := readWholeLine(rc)
if err != nil {
return nil
}
fields := strings.Split(string(line), ":")
if len(fields) < 7 {
return nil
}
uid, err := strconv.ParseUint(fields[2], 10, 32)
if err != nil {
return nil
}
gid, err := strconv.ParseUint(fields[3], 10, 32)
if err != nil {
return nil
}
return &lookupPasswdEntry{
name: fields[0],
uid: uid,
gid: gid,
}
}
func parseNextGroup(rc *bufio.Reader) *lookupGroupEntry {
line, err := readWholeLine(rc)
if err != nil {
return nil
}
fields := strings.Split(string(line), ":")
if len(fields) < 4 {
return nil
}
gid, err := strconv.ParseUint(fields[2], 10, 32)
if err != nil {
return nil
}
return &lookupGroupEntry{
name: fields[0],
gid: gid,
}
}
func lookupUserInContainer(rootdir, username string) (uid uint64, gid uint64, err error) {
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
if err != nil {
return 0, 0, err
}
defer func() {
_ = cmd.Wait()
}()
rc := bufio.NewReader(f)
defer f.Close()
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := parseNextPasswd(rc)
for pwd != nil {
if pwd.name != username {
pwd = parseNextPasswd(rc)
continue
}
return pwd.uid, pwd.gid, nil
}
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
}
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (username string, gid uint64, err error) {
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
if err != nil {
return "", 0, err
}
defer func() {
_ = cmd.Wait()
}()
rc := bufio.NewReader(f)
defer f.Close()
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := parseNextPasswd(rc)
for pwd != nil {
if pwd.uid != userid {
pwd = parseNextPasswd(rc)
continue
}
return pwd.name, pwd.gid, nil
}
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
}
func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) {
cmd, f, err := openChrootedFile(rootdir, "/etc/group")
if err != nil {
return 0, err
}
defer func() {
_ = cmd.Wait()
}()
rc := bufio.NewReader(f)
defer f.Close()
lookupGroup.Lock()
defer lookupGroup.Unlock()
grp := parseNextGroup(rc)
for grp != nil {
if grp.name != groupname {
grp = parseNextGroup(rc)
continue
}
return grp.gid, nil
}
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
}

View File

@@ -1,124 +0,0 @@
// +build cgo
// +build linux
package buildah
// #include <sys/types.h>
// #include <grp.h>
// #include <pwd.h>
// #include <stdlib.h>
// #include <stdio.h>
// #include <string.h>
// typedef FILE * pFILE;
import "C"
import (
"fmt"
"os/user"
"path/filepath"
"sync"
"syscall"
"unsafe"
"github.com/pkg/errors"
)
func fopenContainerFile(rootdir, filename string) (C.pFILE, error) {
var st, lst syscall.Stat_t
ctrfile := filepath.Join(rootdir, filename)
cctrfile := C.CString(ctrfile)
defer C.free(unsafe.Pointer(cctrfile))
mode := C.CString("r")
defer C.free(unsafe.Pointer(mode))
f, err := C.fopen(cctrfile, mode)
if f == nil || err != nil {
return nil, errors.Wrapf(err, "error opening %q", ctrfile)
}
if err = syscall.Fstat(int(C.fileno(f)), &st); err != nil {
return nil, errors.Wrapf(err, "fstat(%q)", ctrfile)
}
if err = syscall.Lstat(ctrfile, &lst); err != nil {
return nil, errors.Wrapf(err, "lstat(%q)", ctrfile)
}
if st.Dev != lst.Dev || st.Ino != lst.Ino {
return nil, errors.Errorf("%q is not a regular file", ctrfile)
}
return f, nil
}
var (
lookupUser, lookupGroup sync.Mutex
)
func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
name := C.CString(username)
defer C.free(unsafe.Pointer(name))
f, err := fopenContainerFile(rootdir, "/etc/passwd")
if err != nil {
return 0, 0, err
}
defer C.fclose(f)
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := C.fgetpwent(f)
for pwd != nil {
if C.strcmp(pwd.pw_name, name) != 0 {
pwd = C.fgetpwent(f)
continue
}
return uint64(pwd.pw_uid), uint64(pwd.pw_gid), nil
}
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
}
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
f, err := fopenContainerFile(rootdir, "/etc/passwd")
if err != nil {
return "", 0, err
}
defer C.fclose(f)
lookupUser.Lock()
defer lookupUser.Unlock()
pwd := C.fgetpwent(f)
for pwd != nil {
if uint64(pwd.pw_uid) != userid {
pwd = C.fgetpwent(f)
continue
}
return C.GoString(pwd.pw_name), uint64(pwd.pw_gid), nil
}
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
}
func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
name := C.CString(groupname)
defer C.free(unsafe.Pointer(name))
f, err := fopenContainerFile(rootdir, "/etc/group")
if err != nil {
return 0, err
}
defer C.fclose(f)
lookupGroup.Lock()
defer lookupGroup.Unlock()
grp := C.fgetgrent(f)
for grp != nil {
if C.strcmp(grp.gr_name, name) != 0 {
grp = C.fgetgrent(f)
continue
}
return uint64(grp.gr_gid), nil
}
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
}

View File

@@ -1,27 +1,121 @@
package util
import (
"path"
"strings"
"github.com/containers/image/docker/reference"
"github.com/containers/image/pkg/sysregistries"
is "github.com/containers/image/storage"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// ExpandTags takes unqualified names, parses them as image names, and returns
// the fully expanded result, including a tag.
func ExpandTags(tags []string) ([]string, error) {
expanded := []string{}
for _, tag := range tags {
name, err := reference.ParseNormalizedNamed(tag)
const (
minimumTruncatedIDLength = 3
)
var (
// RegistryDefaultPathPrefix contains a per-registry listing of default prefixes
// to prepend to image names that only contain a single path component.
RegistryDefaultPathPrefix = map[string]string{
"index.docker.io": "library",
"docker.io": "library",
}
)
// ResolveName checks if name is a valid image name, and if that name doesn't include a domain
// portion, returns a list of the names which it might correspond to in the registries.
func ResolveName(name string, firstRegistry string, sc *types.SystemContext, store storage.Store) []string {
if name == "" {
return nil
}
// Maybe it's a truncated image ID. Don't prepend a registry name, then.
if len(name) >= minimumTruncatedIDLength {
if img, err := store.Image(name); err == nil && img != nil && strings.HasPrefix(img.ID, name) {
// It's a truncated version of the ID of an image that's present in local storage;
// we need to expand the ID.
return []string{img.ID}
}
}
// If the image name already included a domain component, we're done.
named, err := reference.ParseNormalizedNamed(name)
if err != nil {
return []string{name}
}
if named.String() == name {
// Parsing produced the same result, so there was a domain name in there to begin with.
return []string{name}
}
if reference.Domain(named) != "" && RegistryDefaultPathPrefix[reference.Domain(named)] != "" {
// If this domain can cause us to insert something in the middle, check if that happened.
repoPath := reference.Path(named)
domain := reference.Domain(named)
defaultPrefix := RegistryDefaultPathPrefix[reference.Domain(named)] + "/"
if strings.HasPrefix(repoPath, defaultPrefix) && path.Join(domain, repoPath[len(defaultPrefix):]) == name {
// Yup, parsing just inserted a bit in the middle, so there was a domain name there to begin with.
return []string{name}
}
}
// Figure out the list of registries.
registries, err := sysregistries.GetRegistries(sc)
if err != nil {
logrus.Debugf("unable to complete image name %q: %v", name, err)
return []string{name}
}
if sc.DockerInsecureSkipTLSVerify {
if unverifiedRegistries, err := sysregistries.GetInsecureRegistries(sc); err == nil {
registries = append(registries, unverifiedRegistries...)
}
}
// Create all of the combinations. Some registries need an additional component added, so
// use our lookaside map to keep track of them. If there are no configured registries, at
// least return the name as it was passed to us.
candidates := []string{}
for _, registry := range append([]string{firstRegistry}, registries...) {
if registry == "" {
continue
}
middle := ""
if prefix, ok := RegistryDefaultPathPrefix[registry]; ok && strings.IndexRune(name, '/') == -1 {
middle = prefix
}
candidate := path.Join(registry, middle, name)
candidates = append(candidates, candidate)
}
if len(candidates) == 0 {
candidates = append(candidates, name)
}
return candidates
}
// ExpandNames takes unqualified names, parses them as image names, and returns
// the fully expanded result, including a tag. Names which don't include a registry
// name will be marked for the most-preferred registry (i.e., the first one in our
// configuration).
func ExpandNames(names []string) ([]string, error) {
expanded := make([]string, 0, len(names))
for _, n := range names {
name, err := reference.ParseNormalizedNamed(n)
if err != nil {
return nil, errors.Wrapf(err, "error parsing tag %q", tag)
return nil, errors.Wrapf(err, "error parsing name %q", n)
}
name = reference.TagNameOnly(name)
tag = ""
tag := ""
digest := ""
if tagged, ok := name.(reference.NamedTagged); ok {
tag = ":" + tagged.Tag()
}
expanded = append(expanded, name.Name()+tag)
if digested, ok := name.(reference.Digested); ok {
digest = "@" + digested.Digest().String()
}
expanded = append(expanded, name.Name()+tag+digest)
}
return expanded, nil
}
@@ -48,7 +142,7 @@ func FindImage(store storage.Store, image string) (*storage.Image, error) {
// AddImageNames adds the specified names to the specified image.
func AddImageNames(store storage.Store, image *storage.Image, addNames []string) error {
names, err := ExpandTags(addNames)
names, err := ExpandNames(addNames)
if err != nil {
return err
}

View File

@@ -1,8 +1,8 @@
github.com/BurntSushi/toml master
github.com/Nvveen/Gotty master
github.com/blang/semver master
github.com/containers/image 063852766c3e82ec8359ce5f6612e056f3efaa76
github.com/containers/storage d7921c6facc516358070a1306689eda18adaa20a
github.com/containers/image master
github.com/containers/storage master
github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716
github.com/docker/docker 30eb4d8cdc422b023d5f11f29a82ecb73554183b
github.com/docker/engine-api master
@@ -36,7 +36,7 @@ github.com/sirupsen/logrus master
github.com/syndtr/gocapability master
github.com/tchap/go-patricia master
github.com/urfave/cli master
github.com/vbatts/tar-split master
github.com/vbatts/tar-split v0.10.2
golang.org/x/crypto master
golang.org/x/net master
golang.org/x/sys master

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,12 @@ func (s *dirImageSource) Close() error {
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
// It may use a remote (= slow) service.
func (s *dirImageSource) GetManifest() ([]byte, string, error) {
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
func (s *dirImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
if instanceDigest != nil {
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
}
m, err := ioutil.ReadFile(s.ref.manifestPath())
if err != nil {
return nil, "", err
@@ -43,10 +48,6 @@ func (s *dirImageSource) GetManifest() ([]byte, string, error) {
return m, manifest.GuessMIMEType(m), err
}
func (s *dirImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
}
// GetBlob returns a stream for the specified blob, and the blobs size (or -1 if unknown).
func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
r, err := os.Open(s.ref.layerPath(info.Digest))
@@ -60,7 +61,14 @@ func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, err
return r, fi.Size(), nil
}
func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
// (e.g. if the source never returns manifest lists).
func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
if instanceDigest != nil {
return nil, errors.Errorf(`Manifests lists are not supported by "dir:"`)
}
signatures := [][]byte{}
for i := 0; ; i++ {
signature, err := ioutil.ReadFile(s.ref.signaturePath(i))
@@ -74,3 +82,8 @@ func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
}
return signatures, nil
}
// LayerInfosForCopy() returns updated layer info that should be used when copying, in preference to values in the manifest, if specified.
func (s *dirImageSource) LayerInfosForCopy() []types.BlobInfo {
return nil
}

View File

@@ -134,13 +134,14 @@ func (ref dirReference) PolicyConfigurationNamespaces() []string {
return res
}
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned Image.
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
func (ref dirReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (ref dirReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
src := newImageSource(ref)
return image.FromSource(src)
return image.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.
@@ -152,7 +153,11 @@ func (ref dirReference) NewImageSource(ctx *types.SystemContext) (types.ImageSou
// NewImageDestination returns a types.ImageDestination for this reference.
// The caller must call .Close() on the returned ImageDestination.
func (ref dirReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
return newImageDestination(ref), nil
compress := false
if ctx != nil {
compress = ctx.DirForceCompress
}
return newImageDestination(ref, compress)
}
// DeleteImage deletes the named image from the registry, if supported.
@@ -175,3 +180,8 @@ func (ref dirReference) layerPath(digest digest.Digest) string {
func (ref dirReference) signaturePath(index int) string {
return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1))
}
// versionPath returns a path for the version file within a directory using our conventions.
func (ref dirReference) versionPath() string {
return filepath.Join(ref.path, "version")
}

View File

@@ -34,3 +34,8 @@ func (s *archiveImageSource) Reference() types.ImageReference {
func (s *archiveImageSource) Close() error {
return nil
}
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
func (s *archiveImageSource) LayerInfosForCopy() []types.BlobInfo {
return nil
}

View File

@@ -125,13 +125,14 @@ func (ref archiveReference) PolicyConfigurationNamespaces() []string {
return []string{}
}
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned Image.
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
func (ref archiveReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (ref archiveReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
src := newImageSource(ctx, ref)
return ctrImage.FromSource(src)
return ctrImage.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.

View File

@@ -0,0 +1,69 @@
package daemon
import (
"net/http"
"path/filepath"
"github.com/containers/image/types"
dockerclient "github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
)
const (
// The default API version to be used in case none is explicitly specified
defaultAPIVersion = "1.22"
)
// NewDockerClient initializes a new API client based on the passed SystemContext.
func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
host := dockerclient.DefaultDockerHost
if ctx != nil && ctx.DockerDaemonHost != "" {
host = ctx.DockerDaemonHost
}
// Sadly, unix:// sockets don't work transparently with dockerclient.NewClient.
// They work fine with a nil httpClient; with a non-nil httpClient, the transports
// TLSClientConfig must be nil (or the client will try using HTTPS over the PF_UNIX socket
// regardless of the values in the *tls.Config), and we would have to call sockets.ConfigureTransport.
//
// We don't really want to configure anything for unix:// sockets, so just pass a nil *http.Client.
proto, _, _, err := dockerclient.ParseHost(host)
if err != nil {
return nil, err
}
var httpClient *http.Client
if proto != "unix" {
hc, err := tlsConfig(ctx)
if err != nil {
return nil, err
}
httpClient = hc
}
return dockerclient.NewClient(host, defaultAPIVersion, httpClient, nil)
}
func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
options := tlsconfig.Options{}
if ctx != nil && ctx.DockerDaemonInsecureSkipTLSVerify {
options.InsecureSkipVerify = true
}
if ctx != nil && ctx.DockerDaemonCertPath != "" {
options.CAFile = filepath.Join(ctx.DockerDaemonCertPath, "ca.pem")
options.CertFile = filepath.Join(ctx.DockerDaemonCertPath, "cert.pem")
options.KeyFile = filepath.Join(ctx.DockerDaemonCertPath, "key.pem")
}
tlsc, err := tlsconfig.Client(options)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
CheckRedirect: dockerclient.CheckRedirect,
}, nil
}

View File

@@ -14,6 +14,7 @@ import (
type daemonImageDestination struct {
ref daemonReference
mustMatchRuntimeOS bool
*tarfile.Destination // Implements most of types.ImageDestination
// For talking to imageLoadGoroutine
goroutineCancel context.CancelFunc
@@ -24,7 +25,7 @@ type daemonImageDestination struct {
}
// newImageDestination returns a types.ImageDestination for the specified image reference.
func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
if ref.ref == nil {
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
}
@@ -33,7 +34,12 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
}
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
var mustMatchRuntimeOS = true
if ctx != nil && ctx.DockerDaemonHost != client.DefaultDockerHost {
mustMatchRuntimeOS = false
}
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
@@ -42,16 +48,17 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
// Commit() may never be called, so we may never read from this channel; so, make this buffered to allow imageLoadGoroutine to write status and terminate even if we never read it.
statusChannel := make(chan error, 1)
ctx, goroutineCancel := context.WithCancel(context.Background())
go imageLoadGoroutine(ctx, c, reader, statusChannel)
goroutineContext, goroutineCancel := context.WithCancel(context.Background())
go imageLoadGoroutine(goroutineContext, c, reader, statusChannel)
return &daemonImageDestination{
ref: ref,
Destination: tarfile.NewDestination(writer, namedTaggedRef),
goroutineCancel: goroutineCancel,
statusChannel: statusChannel,
writer: writer,
committed: false,
ref: ref,
mustMatchRuntimeOS: mustMatchRuntimeOS,
Destination: tarfile.NewDestination(writer, namedTaggedRef),
goroutineCancel: goroutineCancel,
statusChannel: statusChannel,
writer: writer,
committed: false,
}, nil
}
@@ -80,7 +87,7 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
func (d *daemonImageDestination) MustMatchRuntimeOS() bool {
return true
return d.mustMatchRuntimeOS
}
// Close removes resources associated with an initialized ImageDestination, if any.

View File

@@ -6,14 +6,12 @@ import (
"os"
"github.com/containers/image/docker/tarfile"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
type daemonImageSource struct {
ref daemonReference
*tarfile.Source // Implements most of types.ImageSource
@@ -35,7 +33,7 @@ type layerInfo struct {
// is the config, and that the following len(RootFS) files are the layers, but that feels
// way too brittle.)
func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageSource, error) {
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
@@ -48,7 +46,7 @@ func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageS
defer inputStream.Close()
// FIXME: use SystemContext here.
tarCopyFile, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-daemon-tar")
tarCopyFile, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-daemon-tar")
if err != nil {
return nil, err
}
@@ -83,3 +81,8 @@ func (s *daemonImageSource) Reference() types.ImageReference {
func (s *daemonImageSource) Close() error {
return os.Remove(s.tarCopyPath)
}
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
func (s *daemonImageSource) LayerInfosForCopy() []types.BlobInfo {
return nil
}

View File

@@ -151,14 +151,17 @@ func (ref daemonReference) PolicyConfigurationNamespaces() []string {
return []string{}
}
// NewImage returns a types.Image for this reference.
// The caller must call .Close() on the returned Image.
func (ref daemonReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (ref daemonReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
src, err := newImageSource(ctx, ref)
if err != nil {
return nil, err
}
return image.FromSource(src)
return image.FromSource(ctx, src)
}
// NewImageSource returns a types.ImageSource for this reference.

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