# This code is stolen directly from lxd-images, for expediency's sake. import atexit import hashlib import io import json import os import shutil import subprocess import tarfile import tempfile import uuid def find_on_path(command): """Is command on the executable search path?""" if 'PATH' not in os.environ: return False path = os.environ['PATH'] for element in path.split(os.pathsep): if not element: continue filename = os.path.join(element, command) if os.path.isfile(filename) and os.access(filename, os.X_OK): return True return False class Busybox(object): workdir = None def __init__(self): # Create our workdir self.workdir = tempfile.mkdtemp() def cleanup(self): if self.workdir: shutil.rmtree(self.workdir) def create_tarball(self, split=False): xz = "pxz" if find_on_path("pxz") else "xz" destination_tar = os.path.join(self.workdir, "busybox.tar") target_tarball = tarfile.open(destination_tar, "w:") if split: destination_tar_rootfs = os.path.join(self.workdir, "busybox.rootfs.tar") target_tarball_rootfs = tarfile.open(destination_tar_rootfs, "w:") metadata = {'architecture': os.uname()[4], 'creation_date': int(os.stat("/bin/busybox").st_ctime), 'properties': { 'os': "Busybox", 'architecture': os.uname()[4], 'description': "Busybox %s" % os.uname()[4], 'name': "busybox-%s" % os.uname()[4], # Don't overwrite actual busybox images. 'obfuscate': str(uuid.uuid4()), }, } # Add busybox with open("/bin/busybox", "rb") as fd: busybox_file = tarfile.TarInfo() busybox_file.size = os.stat("/bin/busybox").st_size busybox_file.mode = 0o755 if split: busybox_file.name = "bin/busybox" target_tarball_rootfs.addfile(busybox_file, fd) else: busybox_file.name = "rootfs/bin/busybox" target_tarball.addfile(busybox_file, fd) # Add symlinks busybox = subprocess.Popen(["/bin/busybox", "--list-full"], stdout=subprocess.PIPE, universal_newlines=True) busybox.wait() for path in busybox.stdout.read().split("\n"): if not path.strip(): continue symlink_file = tarfile.TarInfo() symlink_file.type = tarfile.SYMTYPE symlink_file.linkname = "/bin/busybox" if split: symlink_file.name = "%s" % path.strip() target_tarball_rootfs.addfile(symlink_file) else: symlink_file.name = "rootfs/%s" % path.strip() target_tarball.addfile(symlink_file) # Add directories for path in ("dev", "mnt", "proc", "root", "sys", "tmp"): directory_file = tarfile.TarInfo() directory_file.type = tarfile.DIRTYPE if split: directory_file.name = "%s" % path target_tarball_rootfs.addfile(directory_file) else: directory_file.name = "rootfs/%s" % path target_tarball.addfile(directory_file) # Add the metadata file metadata_yaml = json.dumps(metadata, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False).encode('utf-8') + b"\n" metadata_file = tarfile.TarInfo() metadata_file.size = len(metadata_yaml) metadata_file.name = "metadata.yaml" target_tarball.addfile(metadata_file, io.BytesIO(metadata_yaml)) # Add an /etc/inittab; this is to work around: # http://lists.busybox.net/pipermail/busybox/2015-November/083618.html # Basically, since there are some hardcoded defaults that misbehave, we # just pass an empty inittab so those aren't applied, and then busybox # doesn't spin forever. inittab = tarfile.TarInfo() inittab.size = 1 inittab.name = "/rootfs/etc/inittab" target_tarball.addfile(inittab, io.BytesIO(b"\n")) target_tarball.close() if split: target_tarball_rootfs.close() # Compress the tarball r = subprocess.call([xz, "-9", destination_tar]) if r: raise Exception("Failed to compress: %s" % destination_tar) if split: r = subprocess.call([xz, "-9", destination_tar_rootfs]) if r: raise Exception("Failed to compress: %s" % destination_tar_rootfs) return destination_tar + ".xz", destination_tar_rootfs + ".xz" else: return destination_tar + ".xz" def create_busybox_image(): busybox = Busybox() atexit.register(busybox.cleanup) path = busybox.create_tarball() with open(path, "rb") as fd: fingerprint = hashlib.sha256(fd.read()).hexdigest() return path, fingerprint