Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
123493895f | ||
|
|
129fb109d5 | ||
|
|
979c945674 | ||
|
|
c77a8d39f1 | ||
|
|
f4151372e5 | ||
|
|
0705787a07 | ||
|
|
a5129ec3eb | ||
|
|
47ac96155f | ||
|
|
8b2b56d9b8 | ||
|
|
544e63de42 | ||
|
|
43a025ebf9 | ||
|
|
6116d6a9bc | ||
|
|
e5aa6c9fc5 | ||
|
|
95ca6c1e1f | ||
|
|
7244ef44fb | ||
|
|
9df6f62a4c | ||
|
|
bf01a80b2b | ||
|
|
ccd3b3fedb | ||
|
|
4b4e25868c | ||
|
|
4d943752fe | ||
|
|
9128a40ada | ||
|
|
8910199181 | ||
|
|
1fc5a49958 | ||
|
|
98f1533731 | ||
|
|
aae843123f | ||
|
|
77804bf256 | ||
|
|
7aaa21d70a | ||
|
|
ee9b8cde5a | ||
|
|
04ea079130 | ||
|
|
2dd03d6741 | ||
|
|
1680a5f0a0 | ||
|
|
5dd1a5f3c9 | ||
|
|
15792b227a | ||
|
|
38d3cddb0c | ||
|
|
a99d5f0798 | ||
|
|
53c3e6434d | ||
|
|
bf40000e72 | ||
|
|
fb99d85b76 | ||
|
|
85476bf093 | ||
|
|
819c227bf2 | ||
|
|
4b23819189 | ||
|
|
b893112a90 | ||
|
|
9fa477e303 | ||
|
|
b7e3320fe4 | ||
|
|
58025ee1be | ||
|
|
7a3bc6efd4 |
1
.papr.sh
@@ -23,6 +23,7 @@ dnf install -y \
|
||||
libselinux-devel \
|
||||
libselinux-utils \
|
||||
make \
|
||||
openssl \
|
||||
ostree-devel \
|
||||
which
|
||||
|
||||
|
||||
34
.papr.yml
@@ -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
|
||||
|
||||
38
.travis.yml
@@ -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
|
||||
|
||||
4
Makefile
@@ -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
|
||||
|
||||
130
README.md
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# [Buildah](https://www.youtube.com/embed/YVk5NgSiUw8) - a tool which facilitates building OCI container images
|
||||
================================================================
|
||||
@@ -19,109 +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 RHEL 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 are the same as Fedora, above.
|
||||
|
||||
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
@@ -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
|
||||
}
|
||||
|
||||
46
buildah.go
@@ -20,7 +20,7 @@ const (
|
||||
// identify working containers.
|
||||
Package = "buildah"
|
||||
// Version for the Package
|
||||
Version = "0.6"
|
||||
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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, "", " ")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -17,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: "",
|
||||
@@ -45,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(`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
162
commit.go
@@ -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 let’s 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
|
||||
@@ -90,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 {
|
||||
@@ -106,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()
|
||||
@@ -125,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))
|
||||
@@ -148,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 {
|
||||
@@ -237,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")
|
||||
}
|
||||
@@ -247,13 +190,13 @@ 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")
|
||||
}
|
||||
@@ -265,7 +208,7 @@ func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error
|
||||
}
|
||||
} 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")
|
||||
}
|
||||
@@ -291,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")
|
||||
@@ -300,36 +243,11 @@ 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, options.ManifestType))
|
||||
if err != nil {
|
||||
|
||||
@@ -16,8 +16,11 @@ func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemCon
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -345,6 +345,7 @@ return 1
|
||||
"
|
||||
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--signature-policy
|
||||
--runtime
|
||||
--runtime-flag
|
||||
@@ -481,6 +482,7 @@ return 1
|
||||
"
|
||||
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--cert-dir
|
||||
--creds
|
||||
--format
|
||||
@@ -629,6 +631,7 @@ return 1
|
||||
"
|
||||
|
||||
local options_with_args="
|
||||
--authfile
|
||||
--cert-dir
|
||||
--creds
|
||||
--name
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
%global shortcommit %(c=%{commit}; echo ${c:0:7})
|
||||
|
||||
Name: buildah
|
||||
Version: 0.6
|
||||
Version: 0.10
|
||||
Release: 1.git%{shortcommit}%{?dist}
|
||||
Summary: A command line tool used to creating OCI Images
|
||||
License: ASL 2.0
|
||||
@@ -43,6 +43,7 @@ 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
|
||||
@@ -88,12 +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
|
||||
@@ -124,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
|
||||
@@ -144,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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -84,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`
|
||||
@@ -95,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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||

|
||||
|
||||
# Buildah Tutorial 1
|
||||
## Building OCI container images
|
||||
|
||||
|
||||
134
docs/tutorials/02-registries-repositories.md
Normal file
@@ -0,0 +1,134 @@
|
||||

|
||||
|
||||
# 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).
|
||||
16
docs/tutorials/tutorials.md
Normal file
@@ -0,0 +1,16 @@
|
||||

|
||||
|
||||
# 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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
36
import.go
@@ -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
@@ -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.
|
||||
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
2888
logos/buildah-logo-source.svg
Normal file
|
After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 27 KiB |
BIN
logos/buildah-logo_large.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
logos/buildah-logo_large_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 12 KiB |
BIN
logos/buildah-logo_medium.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
logos/buildah-logo_medium_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
logos/buildah-logo_reverse_large.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
logos/buildah-logo_reverse_medium.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
logos/buildah-logo_reverse_small.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
BIN
logos/buildah-logo_small.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
logos/buildah-logo_small_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
logos/buildah-logomark_large.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
logos/buildah-logomark_large_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
logos/buildah-logomark_medium.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
logos/buildah-logomark_medium_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
logos/buildah-logomark_small.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
logos/buildah-logomark_small_transparent-bg.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 29 KiB |
1870
logos/buildah.svg
|
Before Width: | Height: | Size: 88 KiB |
193
new.go
@@ -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)
|
||||
|
||||
41
pull.go
@@ -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
|
||||
if err == nil {
|
||||
return destRef, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
26
run.go
@@ -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
@@ -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)
|
||||
}
|
||||
124
tests/byid.bats
Normal 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 ]
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,11 +8,15 @@ 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"
|
||||
@@ -38,3 +42,14 @@ load helpers
|
||||
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
@@ -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
@@ -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 = []
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
########
|
||||
|
||||
193
tests/test_buildah_baseline.sh
Normal 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
@@ -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
|
||||
@@ -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}
|
||||
|
||||
114
util/util.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
github.com/BurntSushi/toml master
|
||||
github.com/Nvveen/Gotty master
|
||||
github.com/blang/semver master
|
||||
github.com/containers/image f950aa3529148eb0dea90888c24b6682da641b13
|
||||
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
|
||||
|
||||
274
vendor/github.com/containers/image/copy/copy.go
generated
vendored
@@ -30,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) {
|
||||
@@ -85,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.
|
||||
@@ -116,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))
|
||||
@@ -134,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")
|
||||
@@ -178,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, options.ForceManifestMIMEType)
|
||||
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
|
||||
@@ -234,9 +272,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
}
|
||||
// If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType.
|
||||
// So if we are here, we will definitely be trying to convert the manifest.
|
||||
// With !canModifyManifest, that would just be a string of repeated failures for the same reason,
|
||||
// With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason,
|
||||
// so let’s bail out early and with a better error message.
|
||||
if !canModifyManifest {
|
||||
if !ic.canModifyManifest {
|
||||
return errors.Wrap(err, "Writing manifest failed (and converting it is not possible)")
|
||||
}
|
||||
|
||||
@@ -244,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)
|
||||
@@ -263,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
|
||||
}
|
||||
}
|
||||
@@ -299,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.
|
||||
@@ -335,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 {
|
||||
@@ -349,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
|
||||
@@ -380,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)
|
||||
@@ -396,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
|
||||
}
|
||||
@@ -438,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
|
||||
@@ -452,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)
|
||||
}
|
||||
@@ -480,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
|
||||
}
|
||||
@@ -514,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
|
||||
}
|
||||
@@ -548,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.
|
||||
@@ -576,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
|
||||
@@ -593,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 {
|
||||
@@ -610,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")
|
||||
}
|
||||
|
||||
25
vendor/github.com/containers/image/copy/manifest.go
generated
vendored
@@ -37,12 +37,12 @@ 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, forceManifestMIMEType string) (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")
|
||||
}
|
||||
@@ -71,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?
|
||||
@@ -98,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
|
||||
}
|
||||
|
||||
14
vendor/github.com/containers/image/copy/sign.go
generated
vendored
@@ -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")
|
||||
|
||||
25
vendor/github.com/containers/image/directory/directory_src.go
generated
vendored
@@ -35,7 +35,12 @@ func (s *dirImageSource) Close() error {
|
||||
|
||||
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
||||
// It may use a remote (= slow) service.
|
||||
func (s *dirImageSource) GetManifest() ([]byte, string, error) {
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *dirImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
|
||||
}
|
||||
m, err := ioutil.ReadFile(s.ref.manifestPath())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
@@ -43,10 +48,6 @@ func (s *dirImageSource) GetManifest() ([]byte, string, error) {
|
||||
return m, manifest.GuessMIMEType(m), err
|
||||
}
|
||||
|
||||
func (s *dirImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`)
|
||||
}
|
||||
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
r, err := os.Open(s.ref.layerPath(info.Digest))
|
||||
@@ -60,7 +61,14 @@ func (s *dirImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, err
|
||||
return r, fi.Size(), nil
|
||||
}
|
||||
|
||||
func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
|
||||
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
|
||||
// (e.g. if the source never returns manifest lists).
|
||||
func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if instanceDigest != nil {
|
||||
return nil, errors.Errorf(`Manifests lists are not supported by "dir:"`)
|
||||
}
|
||||
signatures := [][]byte{}
|
||||
for i := 0; ; i++ {
|
||||
signature, err := ioutil.ReadFile(s.ref.signaturePath(i))
|
||||
@@ -74,3 +82,8 @@ func (s *dirImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
|
||||
}
|
||||
return signatures, nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when copying, in preference to values in the manifest, if specified.
|
||||
func (s *dirImageSource) LayerInfosForCopy() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
9
vendor/github.com/containers/image/directory/directory_transport.go
generated
vendored
@@ -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.
|
||||
|
||||
5
vendor/github.com/containers/image/docker/archive/src.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
9
vendor/github.com/containers/image/docker/archive/transport.go
generated
vendored
@@ -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.
|
||||
|
||||
21
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
@@ -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
|
||||
@@ -33,6 +34,11 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
|
||||
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
||||
}
|
||||
|
||||
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")
|
||||
@@ -46,12 +52,13 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
|
||||
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.
|
||||
|
||||
10
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
@@ -6,13 +6,12 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/containers/image/docker/tarfile"
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
|
||||
|
||||
type daemonImageSource struct {
|
||||
ref daemonReference
|
||||
*tarfile.Source // Implements most of types.ImageSource
|
||||
@@ -47,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
|
||||
}
|
||||
@@ -82,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
|
||||
}
|
||||
|
||||
11
vendor/github.com/containers/image/docker/daemon/daemon_transport.go
generated
vendored
@@ -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.
|
||||
|
||||
10
vendor/github.com/containers/image/docker/docker_image.go
generated
vendored
@@ -12,26 +12,26 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Image is a Docker-specific implementation of types.Image with a few extra methods
|
||||
// Image is a Docker-specific implementation of types.ImageCloser with a few extra methods
|
||||
// which are specific to Docker.
|
||||
type Image struct {
|
||||
types.Image
|
||||
types.ImageCloser
|
||||
src *dockerImageSource
|
||||
}
|
||||
|
||||
// newImage returns a new Image interface type after setting up
|
||||
// a client to the registry hosting the given image.
|
||||
// The caller must call .Close() on the returned Image.
|
||||
func newImage(ctx *types.SystemContext, ref dockerReference) (types.Image, error) {
|
||||
func newImage(ctx *types.SystemContext, ref dockerReference) (types.ImageCloser, error) {
|
||||
s, err := newImageSource(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := image.FromSource(s)
|
||||
img, err := image.FromSource(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Image{Image: img, src: s}, nil
|
||||
return &Image{ImageCloser: img, src: s}, nil
|
||||
}
|
||||
|
||||
// SourceRefFullName returns a fully expanded name for the repository this image is in.
|
||||
|
||||
48
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@@ -52,6 +52,11 @@ func (s *dockerImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (s *dockerImageSource) LayerInfosForCopy() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
// simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1)
|
||||
// Alternatively, an empty string is returned unchanged, and invalid values are "simplified" to an empty string.
|
||||
func simplifyContentType(contentType string) string {
|
||||
@@ -67,7 +72,12 @@ func simplifyContentType(contentType string) string {
|
||||
|
||||
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
||||
// It may use a remote (= slow) service.
|
||||
func (s *dockerImageSource) GetManifest() ([]byte, string, error) {
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *dockerImageSource) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
return s.fetchManifest(context.TODO(), instanceDigest.String())
|
||||
}
|
||||
err := s.ensureManifestIsLoaded(context.TODO())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
@@ -94,18 +104,12 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
|
||||
return manblob, simplifyContentType(res.Header.Get("Content-Type")), nil
|
||||
}
|
||||
|
||||
// GetTargetManifest returns an image's manifest given a digest.
|
||||
// This is mainly used to retrieve a single image's manifest out of a manifest list.
|
||||
func (s *dockerImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
return s.fetchManifest(context.TODO(), digest.String())
|
||||
}
|
||||
|
||||
// ensureManifestIsLoaded sets s.cachedManifest and s.cachedManifestMIMEType
|
||||
//
|
||||
// ImageSource implementations are not required or expected to do any caching,
|
||||
// but because our signatures are “attached” to the manifest digest,
|
||||
// we need to ensure that the digest of the manifest returned by GetManifest
|
||||
// and used by GetSignatures are consistent, otherwise we would get spurious
|
||||
// we need to ensure that the digest of the manifest returned by GetManifest(nil)
|
||||
// and used by GetSignatures(ctx, nil) are consistent, otherwise we would get spurious
|
||||
// signature verification failures when pulling while a tag is being updated.
|
||||
func (s *dockerImageSource) ensureManifestIsLoaded(ctx context.Context) error {
|
||||
if s.cachedManifest != nil {
|
||||
@@ -176,22 +180,30 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64,
|
||||
return res.Body, getBlobSize(res), nil
|
||||
}
|
||||
|
||||
func (s *dockerImageSource) GetSignatures(ctx context.Context) ([][]byte, error) {
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
|
||||
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
|
||||
// (e.g. if the source never returns manifest lists).
|
||||
func (s *dockerImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if err := s.c.detectProperties(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case s.c.signatureBase != nil:
|
||||
return s.getSignaturesFromLookaside(ctx)
|
||||
return s.getSignaturesFromLookaside(ctx, instanceDigest)
|
||||
case s.c.supportsSignatures:
|
||||
return s.getSignaturesFromAPIExtension(ctx)
|
||||
return s.getSignaturesFromAPIExtension(ctx, instanceDigest)
|
||||
default:
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// manifestDigest returns a digest of the manifest, either from the supplied reference or from a fetched manifest.
|
||||
func (s *dockerImageSource) manifestDigest(ctx context.Context) (digest.Digest, error) {
|
||||
// manifestDigest returns a digest of the manifest, from instanceDigest if non-nil; or from the supplied reference,
|
||||
// or finally, from a fetched manifest.
|
||||
func (s *dockerImageSource) manifestDigest(ctx context.Context, instanceDigest *digest.Digest) (digest.Digest, error) {
|
||||
if instanceDigest != nil {
|
||||
return *instanceDigest, nil
|
||||
}
|
||||
if digested, ok := s.ref.ref.(reference.Digested); ok {
|
||||
d := digested.Digest()
|
||||
if d.Algorithm() == digest.Canonical {
|
||||
@@ -206,8 +218,8 @@ func (s *dockerImageSource) manifestDigest(ctx context.Context) (digest.Digest,
|
||||
|
||||
// getSignaturesFromLookaside implements GetSignatures() from the lookaside location configured in s.c.signatureBase,
|
||||
// which is not nil.
|
||||
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context) ([][]byte, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx)
|
||||
func (s *dockerImageSource) getSignaturesFromLookaside(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -276,8 +288,8 @@ func (s *dockerImageSource) getOneSignature(ctx context.Context, url *url.URL) (
|
||||
}
|
||||
|
||||
// getSignaturesFromAPIExtension implements GetSignatures() using the X-Registry-Supports-Signatures API extension.
|
||||
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context) ([][]byte, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx)
|
||||
func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
manifestDigest, err := s.manifestDigest(ctx, instanceDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
7
vendor/github.com/containers/image/docker/docker_transport.go
generated
vendored
@@ -122,11 +122,12 @@ func (ref dockerReference) PolicyConfigurationNamespaces() []string {
|
||||
return policyconfiguration.DockerReferenceNamespaces(ref.ref)
|
||||
}
|
||||
|
||||
// NewImage returns a types.Image for this reference, possibly specialized for this ImageTransport.
|
||||
// The caller must call .Close() on the returned Image.
|
||||
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
|
||||
// The caller must call .Close() on the returned ImageCloser.
|
||||
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
|
||||
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
|
||||
func (ref dockerReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
|
||||
func (ref dockerReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
|
||||
return newImage(ctx, ref)
|
||||
}
|
||||
|
||||
|
||||
11
vendor/github.com/containers/image/docker/tarfile/dest.go
generated
vendored
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/internal/tmpdir"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@@ -18,8 +19,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
|
||||
|
||||
// Destination is a partial implementation of types.ImageDestination for writing to an io.Writer.
|
||||
type Destination struct {
|
||||
writer io.Writer
|
||||
@@ -107,7 +106,7 @@ func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types
|
||||
|
||||
if inputInfo.Size == -1 { // Ouch, we need to stream the blob into a temporary file just to determine the size.
|
||||
logrus.Debugf("docker tarfile: input with unknown size, streaming to disk first ...")
|
||||
streamCopy, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-tarfile-blob")
|
||||
streamCopy, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-tarfile-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
@@ -168,7 +167,7 @@ func (d *Destination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) {
|
||||
func (d *Destination) PutManifest(m []byte) error {
|
||||
// We do not bother with types.ManifestTypeRejectedError; our .SupportedManifestMIMETypes() above is already providing only one alternative,
|
||||
// so the caller trying a different manifest kind would be pointless.
|
||||
var man schema2Manifest
|
||||
var man manifest.Schema2
|
||||
if err := json.Unmarshal(m, &man); err != nil {
|
||||
return errors.Wrap(err, "Error parsing manifest")
|
||||
}
|
||||
@@ -177,12 +176,12 @@ func (d *Destination) PutManifest(m []byte) error {
|
||||
}
|
||||
|
||||
layerPaths := []string{}
|
||||
for _, l := range man.Layers {
|
||||
for _, l := range man.LayersDescriptors {
|
||||
layerPaths = append(layerPaths, l.Digest.String())
|
||||
}
|
||||
|
||||
items := []ManifestItem{{
|
||||
Config: man.Config.Digest.String(),
|
||||
Config: man.ConfigDescriptor.Digest.String(),
|
||||
RepoTags: []string{d.repoTag},
|
||||
Layers: layerPaths,
|
||||
Parent: "",
|
||||
|
||||
46
vendor/github.com/containers/image/docker/tarfile/src.go
generated
vendored
@@ -24,8 +24,8 @@ type Source struct {
|
||||
tarManifest *ManifestItem // nil if not available yet.
|
||||
configBytes []byte
|
||||
configDigest digest.Digest
|
||||
orderedDiffIDList []diffID
|
||||
knownLayers map[diffID]*layerInfo
|
||||
orderedDiffIDList []digest.Digest
|
||||
knownLayers map[digest.Digest]*layerInfo
|
||||
// Other state
|
||||
generatedManifest []byte // Private cache for GetManifest(), nil if not set yet.
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (s *Source) ensureCachedDataIsPresent() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var parsedConfig image // Most fields ommitted, we only care about layer DiffIDs.
|
||||
var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
|
||||
if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
|
||||
return errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
|
||||
}
|
||||
@@ -194,12 +194,12 @@ func (s *Source) LoadTarManifest() ([]ManifestItem, error) {
|
||||
return s.loadTarManifest()
|
||||
}
|
||||
|
||||
func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image) (map[diffID]*layerInfo, error) {
|
||||
func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manifest.Schema2Image) (map[digest.Digest]*layerInfo, error) {
|
||||
// Collect layer data available in manifest and config.
|
||||
if len(tarManifest.Layers) != len(parsedConfig.RootFS.DiffIDs) {
|
||||
return nil, errors.Errorf("Inconsistent layer count: %d in manifest, %d in config", len(tarManifest.Layers), len(parsedConfig.RootFS.DiffIDs))
|
||||
}
|
||||
knownLayers := map[diffID]*layerInfo{}
|
||||
knownLayers := map[digest.Digest]*layerInfo{}
|
||||
unknownLayerSizes := map[string]*layerInfo{} // Points into knownLayers, a "to do list" of items with unknown sizes.
|
||||
for i, diffID := range parsedConfig.RootFS.DiffIDs {
|
||||
if _, ok := knownLayers[diffID]; ok {
|
||||
@@ -249,28 +249,34 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *image
|
||||
|
||||
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
||||
// It may use a remote (= slow) service.
|
||||
func (s *Source) GetManifest() ([]byte, string, error) {
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list);
|
||||
// this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists).
|
||||
func (s *Source) GetManifest(instanceDigest *digest.Digest) ([]byte, string, error) {
|
||||
if instanceDigest != nil {
|
||||
// How did we even get here? GetManifest(nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
if s.generatedManifest == nil {
|
||||
if err := s.ensureCachedDataIsPresent(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
m := schema2Manifest{
|
||||
m := manifest.Schema2{
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2Schema2MediaType,
|
||||
Config: distributionDescriptor{
|
||||
ConfigDescriptor: manifest.Schema2Descriptor{
|
||||
MediaType: manifest.DockerV2Schema2ConfigMediaType,
|
||||
Size: int64(len(s.configBytes)),
|
||||
Digest: s.configDigest,
|
||||
},
|
||||
Layers: []distributionDescriptor{},
|
||||
LayersDescriptors: []manifest.Schema2Descriptor{},
|
||||
}
|
||||
for _, diffID := range s.orderedDiffIDList {
|
||||
li, ok := s.knownLayers[diffID]
|
||||
if !ok {
|
||||
return nil, "", errors.Errorf("Internal inconsistency: Information about layer %s missing", diffID)
|
||||
}
|
||||
m.Layers = append(m.Layers, distributionDescriptor{
|
||||
Digest: digest.Digest(diffID), // diffID is a digest of the uncompressed tarball
|
||||
m.LayersDescriptors = append(m.LayersDescriptors, manifest.Schema2Descriptor{
|
||||
Digest: diffID, // diffID is a digest of the uncompressed tarball
|
||||
MediaType: manifest.DockerV2Schema2LayerMediaType,
|
||||
Size: li.size,
|
||||
})
|
||||
@@ -284,13 +290,6 @@ func (s *Source) GetManifest() ([]byte, string, error) {
|
||||
return s.generatedManifest, manifest.DockerV2Schema2MediaType, nil
|
||||
}
|
||||
|
||||
// GetTargetManifest returns an image's manifest given a digest. This is mainly used to retrieve a single image's manifest
|
||||
// out of a manifest list.
|
||||
func (s *Source) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
// How did we even get here? GetManifest() above has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
|
||||
type readCloseWrapper struct {
|
||||
io.Reader
|
||||
closeFunc func() error
|
||||
@@ -313,7 +312,7 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
return ioutil.NopCloser(bytes.NewReader(s.configBytes)), int64(len(s.configBytes)), nil
|
||||
}
|
||||
|
||||
if li, ok := s.knownLayers[diffID(info.Digest)]; ok { // diffID is a digest of the uncompressed tarball,
|
||||
if li, ok := s.knownLayers[info.Digest]; ok { // diffID is a digest of the uncompressed tarball,
|
||||
stream, err := s.openTarComponent(li.path)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@@ -355,6 +354,13 @@ func (s *Source) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
}
|
||||
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
func (s *Source) GetSignatures(ctx context.Context) ([][]byte, error) {
|
||||
// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for
|
||||
// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list
|
||||
// (e.g. if the source never returns manifest lists).
|
||||
func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
||||
if instanceDigest != nil {
|
||||
// How did we even get here? GetManifest(nil) has returned a manifest.DockerV2Schema2MediaType.
|
||||
return nil, errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`)
|
||||
}
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
|
||||
38
vendor/github.com/containers/image/docker/tarfile/types.go
generated
vendored
@@ -1,6 +1,9 @@
|
||||
package tarfile
|
||||
|
||||
import "github.com/opencontainers/go-digest"
|
||||
import (
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Various data structures.
|
||||
|
||||
@@ -18,37 +21,8 @@ type ManifestItem struct {
|
||||
Config string
|
||||
RepoTags []string
|
||||
Layers []string
|
||||
Parent imageID `json:",omitempty"`
|
||||
LayerSources map[diffID]distributionDescriptor `json:",omitempty"`
|
||||
Parent imageID `json:",omitempty"`
|
||||
LayerSources map[digest.Digest]manifest.Schema2Descriptor `json:",omitempty"`
|
||||
}
|
||||
|
||||
type imageID string
|
||||
type diffID digest.Digest
|
||||
|
||||
// Based on github.com/docker/distribution/blobs.go
|
||||
type distributionDescriptor struct {
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Digest digest.Digest `json:"digest,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
|
||||
// Based on github.com/docker/distribution/manifest/schema2/manifest.go
|
||||
// FIXME: We are repeating this all over the place; make a public copy?
|
||||
type schema2Manifest struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
Config distributionDescriptor `json:"config"`
|
||||
Layers []distributionDescriptor `json:"layers"`
|
||||
}
|
||||
|
||||
// Based on github.com/docker/docker/image/image.go
|
||||
// MOST CONTENT OMITTED AS UNNECESSARY
|
||||
type image struct {
|
||||
RootFS *rootFS `json:"rootfs,omitempty"`
|
||||
}
|
||||
|
||||
type rootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []diffID `json:"diff_ids,omitempty"`
|
||||
}
|
||||
|
||||
56
vendor/github.com/containers/image/image/docker_list.go
generated
vendored
@@ -2,6 +2,7 @@ package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
@@ -21,7 +22,7 @@ type platformSpec struct {
|
||||
|
||||
// A manifestDescriptor references a platform-specific manifest.
|
||||
type manifestDescriptor struct {
|
||||
descriptor
|
||||
manifest.Schema2Descriptor
|
||||
Platform platformSpec `json:"platform"`
|
||||
}
|
||||
|
||||
@@ -31,22 +32,36 @@ type manifestList struct {
|
||||
Manifests []manifestDescriptor `json:"manifests"`
|
||||
}
|
||||
|
||||
func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
list := manifestList{}
|
||||
if err := json.Unmarshal(manblob, &list); err != nil {
|
||||
return nil, err
|
||||
// chooseDigestFromManifestList parses blob as a schema2 manifest list,
|
||||
// and returns the digest of the image appropriate for the current environment.
|
||||
func chooseDigestFromManifestList(ctx *types.SystemContext, blob []byte) (digest.Digest, error) {
|
||||
wantedArch := runtime.GOARCH
|
||||
if ctx != nil && ctx.ArchitectureChoice != "" {
|
||||
wantedArch = ctx.ArchitectureChoice
|
||||
}
|
||||
wantedOS := runtime.GOOS
|
||||
if ctx != nil && ctx.OSChoice != "" {
|
||||
wantedOS = ctx.OSChoice
|
||||
}
|
||||
|
||||
list := manifestList{}
|
||||
if err := json.Unmarshal(blob, &list); err != nil {
|
||||
return "", err
|
||||
}
|
||||
var targetManifestDigest digest.Digest
|
||||
for _, d := range list.Manifests {
|
||||
if d.Platform.Architecture == runtime.GOARCH && d.Platform.OS == runtime.GOOS {
|
||||
targetManifestDigest = d.Digest
|
||||
break
|
||||
if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS {
|
||||
return d.Digest, nil
|
||||
}
|
||||
}
|
||||
if targetManifestDigest == "" {
|
||||
return nil, errors.New("no supported platform found in manifest list")
|
||||
return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS)
|
||||
}
|
||||
|
||||
func manifestSchema2FromManifestList(ctx *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) {
|
||||
targetManifestDigest, err := chooseDigestFromManifestList(ctx, manblob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manblob, mt, err := src.GetTargetManifest(targetManifestDigest)
|
||||
manblob, mt, err := src.GetManifest(&targetManifestDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,5 +74,20 @@ func manifestSchema2FromManifestList(src types.ImageSource, manblob []byte) (gen
|
||||
return nil, errors.Errorf("Manifest image does not match selected manifest digest %s", targetManifestDigest)
|
||||
}
|
||||
|
||||
return manifestInstanceFromBlob(src, manblob, mt)
|
||||
return manifestInstanceFromBlob(ctx, src, manblob, mt)
|
||||
}
|
||||
|
||||
// ChooseManifestInstanceFromManifestList returns a digest of a manifest appropriate
|
||||
// for the current system from the manifest available from src.
|
||||
func ChooseManifestInstanceFromManifestList(ctx *types.SystemContext, src types.UnparsedImage) (digest.Digest, error) {
|
||||
// For now this only handles manifest.DockerV2ListMediaType; we can generalize it later,
|
||||
// probably along with manifest list editing.
|
||||
blob, mt, err := src.Manifest()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if mt != manifest.DockerV2ListMediaType {
|
||||
return "", fmt.Errorf("Internal error: Trying to select an image from a non-manifest-list manifest type %s", mt)
|
||||
}
|
||||
return chooseDigestFromManifestList(ctx, blob)
|
||||
}
|
||||
|
||||
295
vendor/github.com/containers/image/image/docker_schema1.go
generated
vendored
@@ -2,9 +2,6 @@ package image
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
@@ -14,87 +11,25 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
|
||||
)
|
||||
|
||||
type fsLayersSchema1 struct {
|
||||
BlobSum digest.Digest `json:"blobSum"`
|
||||
}
|
||||
|
||||
type historySchema1 struct {
|
||||
V1Compatibility string `json:"v1Compatibility"`
|
||||
}
|
||||
|
||||
// historySchema1 is a string containing this. It is similar to v1Image but not the same, in particular note the ThrowAway field.
|
||||
type v1Compatibility struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig struct {
|
||||
Cmd []string
|
||||
} `json:"container_config,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
ThrowAway bool `json:"throwaway,omitempty"`
|
||||
}
|
||||
|
||||
type manifestSchema1 struct {
|
||||
Name string `json:"name"`
|
||||
Tag string `json:"tag"`
|
||||
Architecture string `json:"architecture"`
|
||||
FSLayers []fsLayersSchema1 `json:"fsLayers"`
|
||||
History []historySchema1 `json:"history"`
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
m *manifest.Schema1
|
||||
}
|
||||
|
||||
func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) {
|
||||
mschema1 := &manifestSchema1{}
|
||||
if err := json.Unmarshal(manifest, mschema1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mschema1.SchemaVersion != 1 {
|
||||
return nil, errors.Errorf("unsupported schema version %d", mschema1.SchemaVersion)
|
||||
}
|
||||
if len(mschema1.FSLayers) != len(mschema1.History) {
|
||||
return nil, errors.New("length of history not equal to number of layers")
|
||||
}
|
||||
if len(mschema1.FSLayers) == 0 {
|
||||
return nil, errors.New("no FSLayers in manifest")
|
||||
}
|
||||
|
||||
if err := fixManifestLayers(mschema1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mschema1, nil
|
||||
}
|
||||
|
||||
// manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data.
|
||||
func manifestSchema1FromComponents(ref reference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest {
|
||||
var name, tag string
|
||||
if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them.
|
||||
name = reference.Path(ref)
|
||||
if tagged, ok := ref.(reference.NamedTagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
return &manifestSchema1{
|
||||
Name: name,
|
||||
Tag: tag,
|
||||
Architecture: architecture,
|
||||
FSLayers: fsLayers,
|
||||
History: history,
|
||||
SchemaVersion: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) serialize() ([]byte, error) {
|
||||
// docker/distribution requires a signature even if the incoming data uses the nominally unsigned DockerV2Schema1MediaType.
|
||||
unsigned, err := json.Marshal(*m)
|
||||
func manifestSchema1FromManifest(manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.Schema1FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return manifest.AddDummyV2S1Signature(unsigned)
|
||||
return &manifestSchema1{m: m}, nil
|
||||
}
|
||||
|
||||
// manifestSchema1FromComponents builds a new manifestSchema1 from the supplied data.
|
||||
func manifestSchema1FromComponents(ref reference.Named, fsLayers []manifest.Schema1FSLayers, history []manifest.Schema1History, architecture string) genericManifest {
|
||||
return &manifestSchema1{m: manifest.Schema1FromComponents(ref, fsLayers, history, architecture)}
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) serialize() ([]byte, error) {
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) manifestMIMEType() string {
|
||||
@@ -104,7 +39,7 @@ func (m *manifestSchema1) manifestMIMEType() string {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestSchema1) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
@@ -128,11 +63,7 @@ func (m *manifestSchema1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestSchema1) LayerInfos() []types.BlobInfo {
|
||||
layers := make([]types.BlobInfo, len(m.FSLayers))
|
||||
for i, layer := range m.FSLayers { // NOTE: This includes empty layers (where m.History.V1Compatibility->ThrowAway)
|
||||
layers[(len(m.FSLayers)-1)-i] = types.BlobInfo{Digest: layer.BlobSum, Size: -1}
|
||||
}
|
||||
return layers
|
||||
return m.m.LayerInfos()
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -153,25 +84,11 @@ func (m *manifestSchema1) EmbeddedDockerReferenceConflicts(ref reference.Named)
|
||||
} else {
|
||||
tag = ""
|
||||
}
|
||||
return m.Name != name || m.Tag != tag
|
||||
return m.m.Name != name || m.m.Tag != tag
|
||||
}
|
||||
|
||||
func (m *manifestSchema1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: m.Tag,
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
return m.m.Inspect(nil)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
@@ -184,25 +101,18 @@ func (m *manifestSchema1) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUp
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m
|
||||
copy := manifestSchema1{m: manifest.Schema1Clone(m.m)}
|
||||
if options.LayerInfos != nil {
|
||||
// Our LayerInfos includes empty layers (where m.History.V1Compatibility->ThrowAway), so expect them to be included here as well.
|
||||
if len(copy.FSLayers) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.FSLayers), len(options.LayerInfos))
|
||||
}
|
||||
for i, info := range options.LayerInfos {
|
||||
// (docker push) sets up m.History.V1Compatibility->{Id,Parent} based on values of info.Digest,
|
||||
// but (docker pull) ignores them in favor of computing DiffIDs from uncompressed data, except verifying the child->parent links and uniqueness.
|
||||
// So, we don't bother recomputing the IDs in m.History.V1Compatibility.
|
||||
copy.FSLayers[(len(options.LayerInfos)-1)-i].BlobSum = info.Digest
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.EmbeddedDockerReference != nil {
|
||||
copy.Name = reference.Path(options.EmbeddedDockerReference)
|
||||
copy.m.Name = reference.Path(options.EmbeddedDockerReference)
|
||||
if tagged, isTagged := options.EmbeddedDockerReference.(reference.NamedTagged); isTagged {
|
||||
copy.Tag = tagged.Tag()
|
||||
copy.m.Tag = tagged.Tag()
|
||||
} else {
|
||||
copy.Tag = ""
|
||||
copy.m.Tag = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +122,21 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
// We have 2 MIME types for schema 1, which are basically equivalent (even the un-"Signed" MIME type will be rejected if there isn’t a signature; so,
|
||||
// handle conversions between them by doing nothing.
|
||||
case manifest.DockerV2Schema2MediaType:
|
||||
return copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
|
||||
m2, err := copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return memoryImageFromManifest(m2), nil
|
||||
case imgspecv1.MediaTypeImageManifest:
|
||||
// We can't directly convert to OCI, but we can transitively convert via a Docker V2.2 Distribution manifest
|
||||
m2, err := copy.convertToManifestSchema2(options.InformationOnly.LayerInfos, options.InformationOnly.LayerDiffIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m2.UpdatedImage(types.ManifestUpdateOptions{
|
||||
ManifestMIMEType: imgspecv1.MediaTypeImageManifest,
|
||||
InformationOnly: options.InformationOnly,
|
||||
})
|
||||
default:
|
||||
return nil, errors.Errorf("Conversion of image manifest from %s to %s is not implemented", manifest.DockerV2Schema1SignedMediaType, options.ManifestMIMEType)
|
||||
}
|
||||
@@ -220,102 +144,32 @@ func (m *manifestSchema1) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
// fixManifestLayers, after validating the supplied manifest
|
||||
// (to use correctly-formatted IDs, and to not have non-consecutive ID collisions in manifest.History),
|
||||
// modifies manifest to only have one entry for each layer ID in manifest.History (deleting the older duplicates,
|
||||
// both from manifest.History and manifest.FSLayers).
|
||||
// Note that even after this succeeds, manifest.FSLayers may contain duplicate entries
|
||||
// (for Dockerfile operations which change the configuration but not the filesystem).
|
||||
func fixManifestLayers(manifest *manifestSchema1) error {
|
||||
type imageV1 struct {
|
||||
ID string
|
||||
Parent string
|
||||
}
|
||||
// Per the specification, we can assume that len(manifest.FSLayers) == len(manifest.History)
|
||||
imgs := make([]*imageV1, len(manifest.FSLayers))
|
||||
for i := range manifest.FSLayers {
|
||||
img := &imageV1{}
|
||||
|
||||
if err := json.Unmarshal([]byte(manifest.History[i].V1Compatibility), img); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgs[i] = img
|
||||
if err := validateV1ID(img.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if imgs[len(imgs)-1].Parent != "" {
|
||||
return errors.New("Invalid parent ID in the base layer of the image")
|
||||
}
|
||||
// check general duplicates to error instead of a deadlock
|
||||
idmap := make(map[string]struct{})
|
||||
var lastID string
|
||||
for _, img := range imgs {
|
||||
// skip IDs that appear after each other, we handle those later
|
||||
if _, exists := idmap[img.ID]; img.ID != lastID && exists {
|
||||
return errors.Errorf("ID %+v appears multiple times in manifest", img.ID)
|
||||
}
|
||||
lastID = img.ID
|
||||
idmap[lastID] = struct{}{}
|
||||
}
|
||||
// backwards loop so that we keep the remaining indexes after removing items
|
||||
for i := len(imgs) - 2; i >= 0; i-- {
|
||||
if imgs[i].ID == imgs[i+1].ID { // repeated ID. remove and continue
|
||||
manifest.FSLayers = append(manifest.FSLayers[:i], manifest.FSLayers[i+1:]...)
|
||||
manifest.History = append(manifest.History[:i], manifest.History[i+1:]...)
|
||||
} else if imgs[i].Parent != imgs[i+1].ID {
|
||||
return errors.Errorf("Invalid parent ID. Expected %v, got %v", imgs[i+1].ID, imgs[i].Parent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateV1ID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return errors.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Based on github.com/docker/docker/distribution/pull_v2.go
|
||||
func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.BlobInfo, layerDiffIDs []digest.Digest) (types.Image, error) {
|
||||
if len(m.History) == 0 {
|
||||
func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.BlobInfo, layerDiffIDs []digest.Digest) (genericManifest, error) {
|
||||
if len(m.m.History) == 0 {
|
||||
// What would this even mean?! Anyhow, the rest of the code depends on fsLayers[0] and history[0] existing.
|
||||
return nil, errors.Errorf("Cannot convert an image with 0 history entries to %s", manifest.DockerV2Schema2MediaType)
|
||||
}
|
||||
if len(m.History) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.History), len(m.FSLayers))
|
||||
if len(m.m.History) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Inconsistent schema 1 manifest: %d history entries, %d fsLayers entries", len(m.m.History), len(m.m.FSLayers))
|
||||
}
|
||||
if uploadedLayerInfos != nil && len(uploadedLayerInfos) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.FSLayers))
|
||||
if uploadedLayerInfos != nil && len(uploadedLayerInfos) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: uploaded %d blobs, but schema1 manifest has %d fsLayers", len(uploadedLayerInfos), len(m.m.FSLayers))
|
||||
}
|
||||
if layerDiffIDs != nil && len(layerDiffIDs) != len(m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.FSLayers))
|
||||
if layerDiffIDs != nil && len(layerDiffIDs) != len(m.m.FSLayers) {
|
||||
return nil, errors.Errorf("Internal error: collected %d DiffID values, but schema1 manifest has %d fsLayers", len(layerDiffIDs), len(m.m.FSLayers))
|
||||
}
|
||||
|
||||
rootFS := rootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: []digest.Digest{},
|
||||
BaseLayer: "",
|
||||
}
|
||||
var layers []descriptor
|
||||
history := make([]imageHistory, len(m.History))
|
||||
for v1Index := len(m.History) - 1; v1Index >= 0; v1Index-- {
|
||||
v2Index := (len(m.History) - 1) - v1Index
|
||||
// Build a list of the diffIDs for the non-empty layers.
|
||||
diffIDs := []digest.Digest{}
|
||||
var layers []manifest.Schema2Descriptor
|
||||
for v1Index := len(m.m.History) - 1; v1Index >= 0; v1Index-- {
|
||||
v2Index := (len(m.m.History) - 1) - v1Index
|
||||
|
||||
var v1compat v1Compatibility
|
||||
if err := json.Unmarshal([]byte(m.History[v1Index].V1Compatibility), &v1compat); err != nil {
|
||||
var v1compat manifest.Schema1V1Compatibility
|
||||
if err := json.Unmarshal([]byte(m.m.History[v1Index].V1Compatibility), &v1compat); err != nil {
|
||||
return nil, errors.Wrapf(err, "Error decoding history entry %d", v1Index)
|
||||
}
|
||||
history[v2Index] = imageHistory{
|
||||
Created: v1compat.Created,
|
||||
Author: v1compat.Author,
|
||||
CreatedBy: strings.Join(v1compat.ContainerConfig.Cmd, " "),
|
||||
Comment: v1compat.Comment,
|
||||
EmptyLayer: v1compat.ThrowAway,
|
||||
}
|
||||
|
||||
if !v1compat.ThrowAway {
|
||||
var size int64
|
||||
if uploadedLayerInfos != nil {
|
||||
@@ -325,54 +179,23 @@ func (m *manifestSchema1) convertToManifestSchema2(uploadedLayerInfos []types.Bl
|
||||
if layerDiffIDs != nil {
|
||||
d = layerDiffIDs[v2Index]
|
||||
}
|
||||
layers = append(layers, descriptor{
|
||||
layers = append(layers, manifest.Schema2Descriptor{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Size: size,
|
||||
Digest: m.FSLayers[v1Index].BlobSum,
|
||||
Digest: m.m.FSLayers[v1Index].BlobSum,
|
||||
})
|
||||
rootFS.DiffIDs = append(rootFS.DiffIDs, d)
|
||||
diffIDs = append(diffIDs, d)
|
||||
}
|
||||
}
|
||||
configJSON, err := configJSONFromV1Config([]byte(m.History[0].V1Compatibility), rootFS, history)
|
||||
configJSON, err := m.m.ToSchema2(diffIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configDescriptor := descriptor{
|
||||
configDescriptor := manifest.Schema2Descriptor{
|
||||
MediaType: "application/vnd.docker.container.image.v1+json",
|
||||
Size: int64(len(configJSON)),
|
||||
Digest: digest.FromBytes(configJSON),
|
||||
}
|
||||
|
||||
m2 := manifestSchema2FromComponents(configDescriptor, nil, configJSON, layers)
|
||||
return memoryImageFromManifest(m2), nil
|
||||
}
|
||||
|
||||
func configJSONFromV1Config(v1ConfigJSON []byte, rootFS rootFS, history []imageHistory) ([]byte, error) {
|
||||
// github.com/docker/docker/image/v1/imagev1.go:MakeConfigFromV1Config unmarshals and re-marshals the input if docker_version is < 1.8.3 to remove blank fields;
|
||||
// we don't do that here. FIXME? Should we? AFAICT it would only affect the digest value of the schema2 manifest, and we don't particularly need that to be
|
||||
// a consistently reproducible value.
|
||||
|
||||
// Preserve everything we don't specifically know about.
|
||||
// (This must be a *json.RawMessage, even though *[]byte is fairly redundant, because only *RawMessage implements json.Marshaler.)
|
||||
rawContents := map[string]*json.RawMessage{}
|
||||
if err := json.Unmarshal(v1ConfigJSON, &rawContents); err != nil { // We have already unmarshaled it before, using a more detailed schema?!
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(rawContents, "id")
|
||||
delete(rawContents, "parent")
|
||||
delete(rawContents, "Size")
|
||||
delete(rawContents, "parent_id")
|
||||
delete(rawContents, "layer_id")
|
||||
delete(rawContents, "throwaway")
|
||||
|
||||
updates := map[string]interface{}{"rootfs": rootFS, "history": history}
|
||||
for field, value := range updates {
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawContents[field] = (*json.RawMessage)(&encoded)
|
||||
}
|
||||
return json.Marshal(rawContents)
|
||||
return manifestSchema2FromComponents(configDescriptor, nil, configJSON, layers), nil
|
||||
}
|
||||
|
||||
152
vendor/github.com/containers/image/image/docker_schema2.go
generated
vendored
@@ -29,54 +29,44 @@ var gzippedEmptyLayer = []byte{
|
||||
// gzippedEmptyLayerDigest is a digest of gzippedEmptyLayer
|
||||
const gzippedEmptyLayerDigest = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
|
||||
|
||||
type descriptor struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Size int64 `json:"size"`
|
||||
Digest digest.Digest `json:"digest"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
}
|
||||
|
||||
type manifestSchema2 struct {
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType"`
|
||||
ConfigDescriptor descriptor `json:"config"`
|
||||
LayersDescriptors []descriptor `json:"layers"`
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
m *manifest.Schema2
|
||||
}
|
||||
|
||||
func manifestSchema2FromManifest(src types.ImageSource, manifest []byte) (genericManifest, error) {
|
||||
v2s2 := manifestSchema2{src: src}
|
||||
if err := json.Unmarshal(manifest, &v2s2); err != nil {
|
||||
func manifestSchema2FromManifest(src types.ImageSource, manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.Schema2FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &v2s2, nil
|
||||
return &manifestSchema2{
|
||||
src: src,
|
||||
m: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// manifestSchema2FromComponents builds a new manifestSchema2 from the supplied data:
|
||||
func manifestSchema2FromComponents(config descriptor, src types.ImageSource, configBlob []byte, layers []descriptor) genericManifest {
|
||||
func manifestSchema2FromComponents(config manifest.Schema2Descriptor, src types.ImageSource, configBlob []byte, layers []manifest.Schema2Descriptor) genericManifest {
|
||||
return &manifestSchema2{
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
SchemaVersion: 2,
|
||||
MediaType: manifest.DockerV2Schema2MediaType,
|
||||
ConfigDescriptor: config,
|
||||
LayersDescriptors: layers,
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
m: manifest.Schema2FromComponents(config, layers),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) manifestMIMEType() string {
|
||||
return m.MediaType
|
||||
return m.m.MediaType
|
||||
}
|
||||
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestSchema2) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{Digest: m.ConfigDescriptor.Digest, Size: m.ConfigDescriptor.Size}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
@@ -105,9 +95,9 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestSchema2")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(types.BlobInfo{
|
||||
Digest: m.ConfigDescriptor.Digest,
|
||||
Size: m.ConfigDescriptor.Size,
|
||||
URLs: m.ConfigDescriptor.URLs,
|
||||
Digest: m.m.ConfigDescriptor.Digest,
|
||||
Size: m.m.ConfigDescriptor.Size,
|
||||
URLs: m.m.ConfigDescriptor.URLs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -118,8 +108,8 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.ConfigDescriptor.Digest)
|
||||
if computedDigest != m.m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.ConfigDescriptor.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
@@ -130,15 +120,7 @@ func (m *manifestSchema2) ConfigBlob() ([]byte, error) {
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestSchema2) LayerInfos() []types.BlobInfo {
|
||||
blobs := []types.BlobInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, types.BlobInfo{
|
||||
Digest: layer.Digest,
|
||||
Size: layer.Size,
|
||||
URLs: layer.URLs,
|
||||
})
|
||||
}
|
||||
return blobs
|
||||
return m.m.LayerInfos()
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -149,24 +131,18 @@ func (m *manifestSchema2) EmbeddedDockerReferenceConflicts(ref reference.Named)
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
getter := func(info types.BlobInfo) ([]byte, error) {
|
||||
if info.Digest != m.ConfigInfo().Digest {
|
||||
// Shouldn't ever happen
|
||||
return nil, errors.New("asked for a different config blob")
|
||||
}
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
return m.m.Inspect(getter)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
@@ -179,17 +155,14 @@ func (m *manifestSchema2) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUp
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
copy := manifestSchema2{ // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
src: m.src,
|
||||
configBlob: m.configBlob,
|
||||
m: manifest.Schema2Clone(m.m),
|
||||
}
|
||||
if options.LayerInfos != nil {
|
||||
if len(copy.LayersDescriptors) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.LayersDescriptors), len(options.LayerInfos))
|
||||
}
|
||||
copy.LayersDescriptors = make([]descriptor, len(options.LayerInfos))
|
||||
for i, info := range options.LayerInfos {
|
||||
copy.LayersDescriptors[i].MediaType = m.LayersDescriptors[i].MediaType
|
||||
copy.LayersDescriptors[i].Digest = info.Digest
|
||||
copy.LayersDescriptors[i].Size = info.Size
|
||||
copy.LayersDescriptors[i].URLs = info.URLs
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1 to schema2, but we really don't care.
|
||||
@@ -207,6 +180,15 @@ func (m *manifestSchema2) UpdatedImage(options types.ManifestUpdateOptions) (typ
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
func oci1DescriptorFromSchema2Descriptor(d manifest.Schema2Descriptor) imgspecv1.Descriptor {
|
||||
return imgspecv1.Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Size: d.Size,
|
||||
Digest: d.Digest,
|
||||
URLs: d.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestSchema2) convertToManifestOCI1() (types.Image, error) {
|
||||
configOCI, err := m.OCIConfig()
|
||||
if err != nil {
|
||||
@@ -217,18 +199,16 @@ func (m *manifestSchema2) convertToManifestOCI1() (types.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := descriptorOCI1{
|
||||
descriptor: descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Size: int64(len(configOCIBytes)),
|
||||
Digest: digest.FromBytes(configOCIBytes),
|
||||
},
|
||||
config := imgspecv1.Descriptor{
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
Size: int64(len(configOCIBytes)),
|
||||
Digest: digest.FromBytes(configOCIBytes),
|
||||
}
|
||||
|
||||
layers := make([]descriptorOCI1, len(m.LayersDescriptors))
|
||||
layers := make([]imgspecv1.Descriptor, len(m.m.LayersDescriptors))
|
||||
for idx := range layers {
|
||||
layers[idx] = descriptorOCI1{descriptor: m.LayersDescriptors[idx]}
|
||||
if m.LayersDescriptors[idx].MediaType == manifest.DockerV2Schema2ForeignLayerMediaType {
|
||||
layers[idx] = oci1DescriptorFromSchema2Descriptor(m.m.LayersDescriptors[idx])
|
||||
if m.m.LayersDescriptors[idx].MediaType == manifest.DockerV2Schema2ForeignLayerMediaType {
|
||||
layers[idx].MediaType = imgspecv1.MediaTypeImageLayerNonDistributable
|
||||
} else {
|
||||
// we assume layers are gzip'ed because docker v2s2 only deals with
|
||||
@@ -247,14 +227,14 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageConfig := &image{}
|
||||
imageConfig := &manifest.Schema2Image{}
|
||||
if err := json.Unmarshal(configBytes, imageConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Build fsLayers and History, discarding all configs. We will patch the top-level config in later.
|
||||
fsLayers := make([]fsLayersSchema1, len(imageConfig.History))
|
||||
history := make([]historySchema1, len(imageConfig.History))
|
||||
fsLayers := make([]manifest.Schema1FSLayers, len(imageConfig.History))
|
||||
history := make([]manifest.Schema1History, len(imageConfig.History))
|
||||
nonemptyLayerIndex := 0
|
||||
var parentV1ID string // Set in the loop
|
||||
v1ID := ""
|
||||
@@ -282,10 +262,10 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
}
|
||||
blobDigest = gzippedEmptyLayerDigest
|
||||
} else {
|
||||
if nonemptyLayerIndex >= len(m.LayersDescriptors) {
|
||||
return nil, errors.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.LayersDescriptors))
|
||||
if nonemptyLayerIndex >= len(m.m.LayersDescriptors) {
|
||||
return nil, errors.Errorf("Invalid image configuration, needs more than the %d distributed layers", len(m.m.LayersDescriptors))
|
||||
}
|
||||
blobDigest = m.LayersDescriptors[nonemptyLayerIndex].Digest
|
||||
blobDigest = m.m.LayersDescriptors[nonemptyLayerIndex].Digest
|
||||
nonemptyLayerIndex++
|
||||
}
|
||||
|
||||
@@ -296,7 +276,7 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
}
|
||||
v1ID = v
|
||||
|
||||
fakeImage := v1Compatibility{
|
||||
fakeImage := manifest.Schema1V1Compatibility{
|
||||
ID: v1ID,
|
||||
Parent: parentV1ID,
|
||||
Comment: historyEntry.Comment,
|
||||
@@ -310,8 +290,8 @@ func (m *manifestSchema2) convertToManifestSchema1(dest types.ImageDestination)
|
||||
return nil, errors.Errorf("Internal error: Error creating v1compatibility for %#v", fakeImage)
|
||||
}
|
||||
|
||||
fsLayers[v1Index] = fsLayersSchema1{BlobSum: blobDigest}
|
||||
history[v1Index] = historySchema1{V1Compatibility: string(v1CompatibilityBytes)}
|
||||
fsLayers[v1Index] = manifest.Schema1FSLayers{BlobSum: blobDigest}
|
||||
history[v1Index] = manifest.Schema1History{V1Compatibility: string(v1CompatibilityBytes)}
|
||||
// Note that parentV1ID of the top layer is preserved when exiting this loop
|
||||
}
|
||||
|
||||
|
||||
82
vendor/github.com/containers/image/image/manifest.go
generated
vendored
@@ -1,57 +1,14 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/pkg/strslice"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Cmd strslice.StrSlice
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
type v1Image struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
ContainerConfig *config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
// Config is the configuration of the container received from the client
|
||||
Config *config `json:"config,omitempty"`
|
||||
// Architecture is the hardware that the image is build and runs on
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
// OS is the operating system used to build and run the image
|
||||
OS string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
type image struct {
|
||||
v1Image
|
||||
History []imageHistory `json:"history,omitempty"`
|
||||
RootFS *rootFS `json:"rootfs,omitempty"`
|
||||
}
|
||||
|
||||
type imageHistory struct {
|
||||
Created time.Time `json:"created"`
|
||||
Author string `json:"author,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||
}
|
||||
|
||||
type rootFS struct {
|
||||
Type string `json:"type"`
|
||||
DiffIDs []digest.Digest `json:"diff_ids,omitempty"`
|
||||
BaseLayer string `json:"base_layer,omitempty"`
|
||||
}
|
||||
|
||||
// genericManifest is an interface for parsing, modifying image manifests and related data.
|
||||
// Note that the public methods are intended to be a subset of types.Image
|
||||
// so that embedding a genericManifest into structs works.
|
||||
@@ -87,43 +44,24 @@ type genericManifest interface {
|
||||
UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error)
|
||||
}
|
||||
|
||||
func manifestInstanceFromBlob(src types.ImageSource, manblob []byte, mt string) (genericManifest, error) {
|
||||
switch mt {
|
||||
// "application/json" is a valid v2s1 value per https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md .
|
||||
// This works for now, when nothing else seems to return "application/json"; if that were not true, the mapping/detection might
|
||||
// need to happen within the ImageSource.
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, "application/json":
|
||||
// manifestInstanceFromBlob returns a genericManifest implementation for (manblob, mt) in src.
|
||||
// If manblob is a manifest list, it implicitly chooses an appropriate image from the list.
|
||||
func manifestInstanceFromBlob(ctx *types.SystemContext, src types.ImageSource, manblob []byte, mt string) (genericManifest, error) {
|
||||
switch manifest.NormalizedMIMEType(mt) {
|
||||
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType:
|
||||
return manifestSchema1FromManifest(manblob)
|
||||
case imgspecv1.MediaTypeImageManifest:
|
||||
return manifestOCI1FromManifest(src, manblob)
|
||||
case manifest.DockerV2Schema2MediaType:
|
||||
return manifestSchema2FromManifest(src, manblob)
|
||||
case manifest.DockerV2ListMediaType:
|
||||
return manifestSchema2FromManifestList(src, manblob)
|
||||
default:
|
||||
// If it's not a recognized manifest media type, or we have failed determining the type, we'll try one last time
|
||||
// to deserialize using v2s1 as per https://github.com/docker/distribution/blob/master/manifests.go#L108
|
||||
// and https://github.com/docker/distribution/blob/master/manifest/schema1/manifest.go#L50
|
||||
//
|
||||
// Crane registries can also return "text/plain", or pretty much anything else depending on a file extension “recognized” in the tag.
|
||||
// This makes no real sense, but it happens
|
||||
// because requests for manifests are
|
||||
// redirected to a content distribution
|
||||
// network which is configured that way. See https://bugzilla.redhat.com/show_bug.cgi?id=1389442
|
||||
return manifestSchema1FromManifest(manblob)
|
||||
return manifestSchema2FromManifestList(ctx, src, manblob)
|
||||
default: // Note that this may not be reachable, manifest.NormalizedMIMEType has a default for unknown values.
|
||||
return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt)
|
||||
}
|
||||
}
|
||||
|
||||
// inspectManifest is an implementation of types.Image.Inspect
|
||||
func inspectManifest(m genericManifest) (*types.ImageInspectInfo, error) {
|
||||
info, err := m.imageInspectInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers := m.LayerInfos()
|
||||
info.Layers = make([]string, len(layers))
|
||||
for i, layer := range layers {
|
||||
info.Layers[i] = layer.Digest.String()
|
||||
}
|
||||
return info, nil
|
||||
return m.imageInspectInfo()
|
||||
}
|
||||
|
||||
13
vendor/github.com/containers/image/image/memory.go
generated
vendored
@@ -33,11 +33,6 @@ func (i *memoryImage) Reference() types.ImageReference {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close removes resources associated with an initialized UnparsedImage, if any.
|
||||
func (i *memoryImage) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the size of the image as stored, if known, or -1 if not.
|
||||
func (i *memoryImage) Size() (int64, error) {
|
||||
return -1, nil
|
||||
@@ -67,7 +62,9 @@ func (i *memoryImage) Inspect() (*types.ImageInspectInfo, error) {
|
||||
return inspectManifest(i.genericManifest)
|
||||
}
|
||||
|
||||
// IsMultiImage returns true if the image's manifest is a list of images, false otherwise.
|
||||
func (i *memoryImage) IsMultiImage() bool {
|
||||
return false
|
||||
// LayerInfosForCopy returns an updated set of layer blob information which may not match the manifest.
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (i *memoryImage) LayerInfosForCopy() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
|
||||
116
vendor/github.com/containers/image/image/oci.go
generated
vendored
@@ -12,41 +12,34 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type descriptorOCI1 struct {
|
||||
descriptor
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type manifestOCI1 struct {
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of ConfigDescriptor.
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
ConfigDescriptor descriptorOCI1 `json:"config"`
|
||||
LayersDescriptors []descriptorOCI1 `json:"layers"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
src types.ImageSource // May be nil if configBlob is not nil
|
||||
configBlob []byte // If set, corresponds to contents of m.Config.
|
||||
m *manifest.OCI1
|
||||
}
|
||||
|
||||
func manifestOCI1FromManifest(src types.ImageSource, manifest []byte) (genericManifest, error) {
|
||||
oci := manifestOCI1{src: src}
|
||||
if err := json.Unmarshal(manifest, &oci); err != nil {
|
||||
func manifestOCI1FromManifest(src types.ImageSource, manifestBlob []byte) (genericManifest, error) {
|
||||
m, err := manifest.OCI1FromManifest(manifestBlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oci, nil
|
||||
return &manifestOCI1{
|
||||
src: src,
|
||||
m: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// manifestOCI1FromComponents builds a new manifestOCI1 from the supplied data:
|
||||
func manifestOCI1FromComponents(config descriptorOCI1, src types.ImageSource, configBlob []byte, layers []descriptorOCI1) genericManifest {
|
||||
func manifestOCI1FromComponents(config imgspecv1.Descriptor, src types.ImageSource, configBlob []byte, layers []imgspecv1.Descriptor) genericManifest {
|
||||
return &manifestOCI1{
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
SchemaVersion: 2,
|
||||
ConfigDescriptor: config,
|
||||
LayersDescriptors: layers,
|
||||
src: src,
|
||||
configBlob: configBlob,
|
||||
m: manifest.OCI1FromComponents(config, layers),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) serialize() ([]byte, error) {
|
||||
return json.Marshal(*m)
|
||||
return m.m.Serialize()
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) manifestMIMEType() string {
|
||||
@@ -56,7 +49,7 @@ func (m *manifestOCI1) manifestMIMEType() string {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
func (m *manifestOCI1) ConfigInfo() types.BlobInfo {
|
||||
return types.BlobInfo{Digest: m.ConfigDescriptor.Digest, Size: m.ConfigDescriptor.Size, Annotations: m.ConfigDescriptor.Annotations}
|
||||
return m.m.ConfigInfo()
|
||||
}
|
||||
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
@@ -67,9 +60,9 @@ func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
|
||||
return nil, errors.Errorf("Internal error: neither src nor configBlob set in manifestOCI1")
|
||||
}
|
||||
stream, _, err := m.src.GetBlob(types.BlobInfo{
|
||||
Digest: m.ConfigDescriptor.Digest,
|
||||
Size: m.ConfigDescriptor.Size,
|
||||
URLs: m.ConfigDescriptor.URLs,
|
||||
Digest: m.m.Config.Digest,
|
||||
Size: m.m.Config.Size,
|
||||
URLs: m.m.Config.URLs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -80,8 +73,8 @@ func (m *manifestOCI1) ConfigBlob() ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
computedDigest := digest.FromBytes(blob)
|
||||
if computedDigest != m.ConfigDescriptor.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.ConfigDescriptor.Digest)
|
||||
if computedDigest != m.m.Config.Digest {
|
||||
return nil, errors.Errorf("Download config.json digest %s does not match expected %s", computedDigest, m.m.Config.Digest)
|
||||
}
|
||||
m.configBlob = blob
|
||||
}
|
||||
@@ -107,11 +100,7 @@ func (m *manifestOCI1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
|
||||
blobs := []types.BlobInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs, MediaType: layer.MediaType})
|
||||
}
|
||||
return blobs
|
||||
return m.m.LayerInfos()
|
||||
}
|
||||
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -122,24 +111,18 @@ func (m *manifestOCI1) EmbeddedDockerReferenceConflicts(ref reference.Named) boo
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
getter := func(info types.BlobInfo) ([]byte, error) {
|
||||
if info.Digest != m.ConfigInfo().Digest {
|
||||
// Shouldn't ever happen
|
||||
return nil, errors.New("asked for a different config blob")
|
||||
}
|
||||
config, err := m.ConfigBlob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
v1 := &v1Image{}
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
return m.m.Inspect(getter)
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
@@ -152,18 +135,14 @@ func (m *manifestOCI1) UpdatedImageNeedsLayerDiffIDs(options types.ManifestUpdat
|
||||
// UpdatedImage returns a types.Image modified according to options.
|
||||
// This does not change the state of the original Image object.
|
||||
func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.Image, error) {
|
||||
copy := *m // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
copy := manifestOCI1{ // NOTE: This is not a deep copy, it still shares slices etc.
|
||||
src: m.src,
|
||||
configBlob: m.configBlob,
|
||||
m: manifest.OCI1Clone(m.m),
|
||||
}
|
||||
if options.LayerInfos != nil {
|
||||
if len(copy.LayersDescriptors) != len(options.LayerInfos) {
|
||||
return nil, errors.Errorf("Error preparing updated manifest: layer count changed from %d to %d", len(copy.LayersDescriptors), len(options.LayerInfos))
|
||||
}
|
||||
copy.LayersDescriptors = make([]descriptorOCI1, len(options.LayerInfos))
|
||||
for i, info := range options.LayerInfos {
|
||||
copy.LayersDescriptors[i].MediaType = m.LayersDescriptors[i].MediaType
|
||||
copy.LayersDescriptors[i].Digest = info.Digest
|
||||
copy.LayersDescriptors[i].Size = info.Size
|
||||
copy.LayersDescriptors[i].Annotations = info.Annotations
|
||||
copy.LayersDescriptors[i].URLs = info.URLs
|
||||
if err := copy.m.UpdateLayerInfos(options.LayerInfos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Ignore options.EmbeddedDockerReference: it may be set when converting from schema1, but we really don't care.
|
||||
@@ -179,17 +158,26 @@ func (m *manifestOCI1) UpdatedImage(options types.ManifestUpdateOptions) (types.
|
||||
return memoryImageFromManifest(©), nil
|
||||
}
|
||||
|
||||
func schema2DescriptorFromOCI1Descriptor(d imgspecv1.Descriptor) manifest.Schema2Descriptor {
|
||||
return manifest.Schema2Descriptor{
|
||||
MediaType: d.MediaType,
|
||||
Size: d.Size,
|
||||
Digest: d.Digest,
|
||||
URLs: d.URLs,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *manifestOCI1) convertToManifestSchema2() (types.Image, error) {
|
||||
// Create a copy of the descriptor.
|
||||
config := m.ConfigDescriptor.descriptor
|
||||
config := schema2DescriptorFromOCI1Descriptor(m.m.Config)
|
||||
|
||||
// The only difference between OCI and DockerSchema2 is the mediatypes. The
|
||||
// media type of the manifest is handled by manifestSchema2FromComponents.
|
||||
config.MediaType = manifest.DockerV2Schema2ConfigMediaType
|
||||
|
||||
layers := make([]descriptor, len(m.LayersDescriptors))
|
||||
layers := make([]manifest.Schema2Descriptor, len(m.m.Layers))
|
||||
for idx := range layers {
|
||||
layers[idx] = m.LayersDescriptors[idx].descriptor
|
||||
layers[idx] = schema2DescriptorFromOCI1Descriptor(m.m.Layers[idx])
|
||||
layers[idx].MediaType = manifest.DockerV2Schema2LayerMediaType
|
||||
}
|
||||
|
||||
|
||||