This repository contains a golang library and CLI for VM images, manage disk images, kernel, initrd and other bootable artifacts in OCI registries. It can build, push and pull those images.
It is inspired directly by ORAS and leverages it, but is opinionated to the ECI use case. As such, it uses elements of OCI Artifacts.
It can store the images in multiple formats:
artifacts
(default): leverage full artifacts mime types, with each layer a different artifactlegacy
: standard mime-types and configs, with each layer a different artifactcontainer
: artifacts placed within a filesystem in an OCI container image, leveraging annotations to indicate where each artifact is
In all cases, annotations are used as well.
Because the container
format uses a standard OCI container image, you can create it using standard docker
tools as well. An example is shown below.
To push an ECI to a registry, you need the following items in a directory:
- a root disk image in any supported format: raw, vhd, vmdk, iso
- a Linux kernel (optional)
- a Linux initrd (optional)
- additional disks (optional)
- a config file, whose contents provide the desired OCI manifest config
Note: If you do not provide a config file, a default will be created, using the following
information. It can be overridden via options; run with --help
to see the options.
- OS: current platform OS
- Arch: current platform arch
- Author:
lfedge/edge-containers
You can push the image as follows:
eci push --root path/to/root.img:raw --kernel path/to/kernel --initrd path/to/initrd --disk path/to/disk1:iso --disk path/to/disk2:vmdk ... --config path/to/config lfedge/eci-nginx:ubuntu-1804-11715
The above uses the default artifacts
format, which assumes that the registry fully supports Artifacts
and will use specialized mime types.
If you wish to use one of the other formats, or do not have a choice as you are using a registry that does
not yet support artifacts, select an alternate format with the --format
flag.
For the legacy
format:
eci push --format legacy --root path/to/root.img:raw --kernel path/to/kernel --initrd path/to/initrd --disk path/to/disk1:iso --disk path/to/disk2:vmdk ... --config path/to/config lfedge/eci-nginx:ubuntu-1804-11715
For the container
format:
eci push --format container --root path/to/root.img:raw --kernel path/to/kernel --initrd path/to/initrd --disk path/to/disk1:iso --disk path/to/disk2:vmdk ... --config path/to/config lfedge/eci-nginx:ubuntu-1804-11715
The eci
command will take care of setting the correct mime types and annotations on all of the objects.
Note that disks, both root and additional, must have the file name, following by a :
and the disk type,
so that consumers know how to interpret them, e.g. to send a disk file whose name is mydisk
and
is of type qcow2:
--disk mydisk:qcow2
Standard docker tools do not support the artifacts
or legacy
format. However, you can build and push
using the container
format with standard docker tools. However, docker does not support
adding annotations to the manifest, except using experimental tools.
To support standard docker tools, we support reading the annotations from the image labels,
which are stored in the config that is generated by docker build
.
To use standard docker tools, you need to do two things:
- Place the artifacts, such as disks or kernel, in your container image filesystem
- Add appropriate labels to the container image
The following Dockerfile is equivalent to the above:
FROM scratch
COPY path/to/root.img /root.img
COPY path/to/kernel /kernel
COPY path/to/initrd /initrd
COPY path/to/disk1 /disk1.iso
COPY path/to/disk2 /disk2.vmdk
Then run:
docker build -t lfedge/eci-nginx:ubuntu-1804-11715 --label "org.lfedge.eci.artifact.root"="/root.img" --label "org.lfedge.eci.artifact.kernel"="/kernel" --label "org.lfedge.eci.artifact.initrd"="/initrd" --label "org.lfedge.eci.artifact.disk-1"="/disk1.iso" --label "org.lfedge.eci.artifact.disk-2"="/disk2.vmdk" .
You can include the labels in the Dockerfile, which makes sense, by adding LABEL
commands:
FROM scratch
COPY path/to/root.img /root.img
COPY path/to/kernel /kernel
COPY path/to/initrd /initrd
COPY path/to/disk1 /disk1.iso
COPY path/to/disk2 /disk2.vmdk
LABEL "org.lfedge.eci.artifact.root"="/root.img"
LABEL "org.lfedge.eci.artifact.kernel"="/kernel"
LABEL "org.lfedge.eci.artifact.initrd"="/initrd"
LABEL "org.lfedge.eci.artifact.disk-1"="/disk1.iso"
LABEL "org.lfedge.eci.artifact.disk-2"="/disk2.vmdk"
And then run:
docker build -t lfedge/eci-nginx:ubuntu-1804-11715 .
Note: if you use the container
format, your config file needs to be in a specific format
for docker to recognize it. This utility builds it for you, and it is recommended you accept
the default. However, if you provide --config
, you can override it. Use at your own risk.
To pull an ECI, you simply need a registry where the components will be downloaded:
eci pull lf-edge/eci-nginx:ubuntu-1804-11715
The above will default to placing artifacts in the current directory. To place them in a different directory:
eci pull --dir foo/bar/ lf-edge/eci-nginx:ubuntu-1804-11715
The eci
command knows how to read the manifest and annotations and determine how to extract the data.
Note that whatever format it is in, it can be pulled "as is" by docker, containerd, go-containerregistry, img or any other tool that knows how to pull OCI images.
The specific standard media types are at docs/mediatypes.md.
In addition to the types, eci
always will add annotations to the layer and config in the manifest describing its purpose.
The specific standard annotations are at docs/annotations.md.
ECI is highly opinionated about the file names. No matter what names you pass to it, it will give the files particular names. These are listed in docs/filenames.md.
A sample manifest for an actual pushed image is below. This is a manifest on docker hub, so the media types are the legacy types, while the annotations provide the purpose.
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:ffb3941df4fe37f22165b124d66e966d93b3dbf2765b736818b57a4516aed94e",
"size": 14,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.config.v1+json",
"org.opencontainers.image.title": "config.json"
}
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:edeaaff3f1774ad2888673770c6d64097e391bc362d7d6fb34982ddf0efd18cb",
"size": 4,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.kernel.layer.v1+kernel",
"org.lfedge.eci.role": "kernel",
"org.opencontainers.image.title": "kernel"
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:da1464fd7ceaf38ff56043bc1774af4fb5cb83ef5358981d78de0b8be5a6fbcb",
"size": 4,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.initrd.layer.v1+cpio",
"org.lfedge.eci.role": "initrd",
"org.opencontainers.image.title": "initrd"
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:deb055d836e44a1dcf0317b0cacac2dbdd36301f82abf787f7849d3f5b916750",
"size": 5,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.disk.layer.v1+raw",
"org.lfedge.eci.role": "disk-root",
"org.opencontainers.image.title": "disk-root-root.raw"
}
}
]
}
How does the pull client - and anything else that might want to pull the artifacts - know what it has in hand? It could be one of:
- a normal OCI container image
- a normal OCI container image with VM artifacts inside
- a legacy format image from this library/utility
- an artifacts format image from this library/utility
The parsing process is as follows:
- Retrieve the manifest.
- If the layers have the special
mediaType
, it is an ECI in artifacts format. - If the layers have the special annotations, it is an ECI in legacy format.
- If the manifest has the special annotations, it is an ECI in container format.
- If the config has the special labels, it is an ECI in container format.
- It is a regular OCI image.
The go library is github.com/lf-edge/edge-containers/pkg/registry
. Docs are available at godoc.org/github.com/lf-edge/edge-containers/pkg/registry.
The eci
tool can be built via make build
, which will deposit the build artifact in dist/bin/eci-<os>-<arch>
, e.g. dist/bin/eci-darwin-amd64
or dist/bin/eci-linux-arm64
. To build it for alternate OSes or architectures, run:
make build OS=<target> ARCH=<target>
e.g.
make build OS=linux ARCH=amd64
make build OS=linux ARCH=amd64