mirror of
https://github.com/linuxkit/linuxkit.git
synced 2026-04-03 08:50:58 +00:00
To work with truly immutable filesystems, rather than ones we sneakily remount `rw`, we are going to use overlay for writeable containers. To leave the final mount as `rootfs`, in the writeable case we make a new `lower` path for the read only filesystem, and leave `rootfs` as a mount point for an overlay, with the writable layer and workdir mounted as a tmpfs on `tmp`. See https://github.com/linuxkit/linuxkit/issues/2288 Signed-off-by: Justin Cormack <justin.cormack@docker.com>
235 lines
6.0 KiB
Go
235 lines
6.0 KiB
Go
package moby
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
type tarWriter interface {
|
|
Close() error
|
|
Flush() error
|
|
Write(b []byte) (n int, err error)
|
|
WriteHeader(hdr *tar.Header) error
|
|
}
|
|
|
|
// This uses Docker to convert a Docker image into a tarball. It would be an improvement if we
|
|
// used the containerd libraries to do this instead locally direct from a local image
|
|
// cache as it would be much simpler.
|
|
|
|
// Unfortunately there are some files that Docker always makes appear in a running image and
|
|
// export shows them. In particular we have no way for a user to specify their own resolv.conf.
|
|
// Even if we were not using docker export to get the image, users of docker build cannot override
|
|
// the resolv.conf either, as it is not writeable and bind mounted in.
|
|
|
|
var exclude = map[string]bool{
|
|
".dockerenv": true,
|
|
"Dockerfile": true,
|
|
"dev/console": true,
|
|
"dev/pts": true,
|
|
"dev/shm": true,
|
|
"etc/hostname": true,
|
|
}
|
|
|
|
var replace = map[string]string{
|
|
"etc/hosts": `127.0.0.1 localhost
|
|
::1 localhost ip6-localhost ip6-loopback
|
|
fe00::0 ip6-localnet
|
|
ff00::0 ip6-mcastprefix
|
|
ff02::1 ip6-allnodes
|
|
ff02::2 ip6-allrouters
|
|
`,
|
|
"etc/resolv.conf": `
|
|
# no resolv.conf configured
|
|
`,
|
|
}
|
|
|
|
// tarPrefix creates the leading directories for a path
|
|
func tarPrefix(path string, tw tarWriter) error {
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
if path[len(path)-1] != byte('/') {
|
|
return fmt.Errorf("path does not end with /: %s", path)
|
|
}
|
|
path = path[:len(path)-1]
|
|
if path[0] == byte('/') {
|
|
return fmt.Errorf("path should be relative: %s", path)
|
|
}
|
|
mkdir := ""
|
|
for _, dir := range strings.Split(path, "/") {
|
|
mkdir = mkdir + dir
|
|
hdr := &tar.Header{
|
|
Name: mkdir,
|
|
Mode: 0755,
|
|
Typeflag: tar.TypeDir,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
mkdir = mkdir + "/"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ImageTar takes a Docker image and outputs it to a tar stream
|
|
func ImageTar(image, prefix string, tw tarWriter, trust bool, pull bool, resolv string) error {
|
|
log.Debugf("image tar: %s %s", image, prefix)
|
|
if prefix != "" && prefix[len(prefix)-1] != byte('/') {
|
|
return fmt.Errorf("prefix does not end with /: %s", prefix)
|
|
}
|
|
|
|
err := tarPrefix(prefix, tw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if pull || trust {
|
|
err := dockerPull(image, pull, trust)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not pull image %s: %v", image, err)
|
|
}
|
|
}
|
|
container, err := dockerCreate(image)
|
|
if err != nil {
|
|
// if the image wasn't found, pull it down. Bail on other errors.
|
|
if strings.Contains(err.Error(), "No such image") {
|
|
err := dockerPull(image, true, trust)
|
|
if err != nil {
|
|
return fmt.Errorf("Could not pull image %s: %v", image, err)
|
|
}
|
|
container, err = dockerCreate(image)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to docker create image %s: %v", image, err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("Failed to create docker image %s: %v", image, err)
|
|
}
|
|
}
|
|
contents, err := dockerExport(container)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to docker export container from container %s: %v", container, err)
|
|
}
|
|
err = dockerRm(container)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to docker rm container %s: %v", container, err)
|
|
}
|
|
|
|
// now we need to filter out some files from the resulting tar archive
|
|
|
|
r := bytes.NewReader(contents)
|
|
tr := tar.NewReader(r)
|
|
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exclude[hdr.Name] {
|
|
log.Debugf("image tar: %s %s exclude %s", image, prefix, hdr.Name)
|
|
_, err = io.Copy(ioutil.Discard, tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if replace[hdr.Name] != "" {
|
|
if hdr.Name != "etc/resolv.conf" || resolv == "" {
|
|
contents := replace[hdr.Name]
|
|
hdr.Size = int64(len(contents))
|
|
hdr.Name = prefix + hdr.Name
|
|
log.Debugf("image tar: %s %s add %s", image, prefix, hdr.Name)
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
buf := bytes.NewBufferString(contents)
|
|
_, err = io.Copy(tw, buf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// replace resolv.conf with specified symlink
|
|
hdr.Name = prefix + hdr.Name
|
|
hdr.Size = 0
|
|
hdr.Typeflag = tar.TypeSymlink
|
|
hdr.Linkname = resolv
|
|
log.Debugf("image tar: %s %s add resolv symlink /etc/resolv.conf -> %s", image, prefix, resolv)
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err = io.Copy(ioutil.Discard, tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
log.Debugf("image tar: %s %s add %s", image, prefix, hdr.Name)
|
|
hdr.Name = prefix + hdr.Name
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(tw, tr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
|
|
func ImageBundle(path string, image string, config []byte, tw tarWriter, trust bool, pull bool, readonly bool) error {
|
|
log.Debugf("image bundle: %s %s cfg: %s", path, image, string(config))
|
|
|
|
// if read only, just unpack in rootfs/ but otherwise set up for overlay
|
|
rootfs := "rootfs"
|
|
if !readonly {
|
|
rootfs = "lower"
|
|
}
|
|
|
|
if err := ImageTar(image, filepath.Join(path, rootfs)+"/", tw, trust, pull, ""); err != nil {
|
|
return err
|
|
}
|
|
hdr := &tar.Header{
|
|
Name: filepath.Join(path, "config.json"),
|
|
Mode: 0644,
|
|
Size: int64(len(config)),
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
buf := bytes.NewBuffer(config)
|
|
if _, err := io.Copy(tw, buf); err != nil {
|
|
return err
|
|
}
|
|
if !readonly {
|
|
// add a tmp directory to be used as a mount point for tmpfs for upper, work
|
|
hdr = &tar.Header{
|
|
Name: filepath.Join(path, "tmp"),
|
|
Mode: 0755,
|
|
Typeflag: tar.TypeDir,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
// add rootfs as merged mount point
|
|
hdr = &tar.Header{
|
|
Name: filepath.Join(path, "rootfs"),
|
|
Mode: 0755,
|
|
Typeflag: tar.TypeDir,
|
|
}
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|