Fork me on GitHub
  ▖     
▃▀▄▄▖▄▄▖
 ▀▖     
        


#!/usr/bin/env python3
#
# usage: plash shallow-copy CONTAINER OUT
# Copy and hard link the filesystem to OUT.
# This will export a root filesystem to OUT with minimal overhead. Directories
# are copied with their metadata, but all files are hard linked. you can
# imagine them pointing to the real file inside the plash data. This takes only
# a short time and saves disk space. The downside is that you have to be very
# carefull to not change files, since this would change the plashdata build
# data and affect other containers.
#
# Examples:
# $ mkdir /tmp/mydir
# $ plash shallow-copy --from ubuntu -- /tmp/mydir/ubuntu
# $ plash shallow-copy --from ubuntu --apt git -- /tmp/mydir/ubuntu-with-git
# $ plash sudo du -sh /tmp/mydir/ubuntu
# 413M    /tmp/mydir/ubuntu
# $ plash sudo du -sh /tmp/mydir/ubuntu-with-git/
# 407M    /tmp/mydir/ubuntu-with-git/
# $ plash sudo du -sh /tmp/mydir/
# 423M    /tmp/mydir/
#
# See https://en.wikipedia.org/wiki/Object_copying#Shallow_copy



from plash import utils
from plash import unshare
import subprocess
import tarfile
import sys
import os
import io

utils.handle_build_args()

container = sys.argv[1]
outdir = sys.argv[2]
nodepath = utils.nodepath_or_die(container)
tmpout = utils.mkdtemp()

unshare.unshare_if_root()
unshare.unshare_if_user()

mountpoint = os.path.join(utils.get_plash_data(), 'mnt')

with utils.catch_and_die([subprocess.CalledProcessError], silent=True):
    subprocess.check_call(['plash-mount', container, mountpoint])

io_bytes = io.BytesIO()
tar = tarfile.open(fileobj=io_bytes, mode='w:xz')

file_list = []
for subdir, dirs, files in os.walk(mountpoint):

    rel_subdir = os.path.relpath(subdir, mountpoint)
    for dir in dirs:
        reldir = os.path.join(rel_subdir, dir)
        realdir = os.path.join(subdir, dir)
        tar.add(realdir, reldir, recursive=False)

    for file in files:
        file_list.append(os.path.join(rel_subdir, file))

tar.close()
io_bytes.seek(0)
subprocess.call(['umount', mountpoint])
tar = tarfile.open(fileobj=io_bytes, mode='r')
tar.extractall(tmpout)

plash_data = utils.get_plash_data()
parts = reversed(nodepath.split('/layer/')[-1])
resolve_paths = [
    os.path.join(plash_data, 'index', i, '_data', 'root') for i in parts
]

for file in file_list:
    target = os.path.join(tmpout, file)
    for resolve_path in resolve_paths:
        src = os.path.join(resolve_path, file)
        try:
            os.link(src, target, follow_symlinks=False)
        except FileNotFoundError:
            pass
        else:
            break

with utils.catch_and_die([OSError]):
    os.rename(tmpout, outdir)