diff options
author | Louis Blin <45168934+lbpdt@users.noreply.github.com> | 2021-03-08 20:36:13 +0000 |
---|---|---|
committer | Louis Blin <45168934+lbpdt@users.noreply.github.com> | 2021-03-23 14:50:42 +0000 |
commit | aae8588182913549435332d0ac120e18d7afdab5 (patch) | |
tree | 1d39c7c857583ec8668df58dc39d71f0b4ae21b8 /pkgs/build-support/docker/stream_layered_image.py | |
parent | 148e686044c37e08062b2df597b85d2898e52408 (diff) |
dockerTools.buildLayeredImage: support fromImage
It is now possible to pass a `fromImage` to `buildLayeredImage` and `streamLayeredImage`, similar to what `buildImage` currently supports. This will prepend the layers of the given base image to the resulting image, while ensuring that at most `maxLayers` are used. It will also ensure that environment variables from the base image are propagated to the final image.
Diffstat (limited to 'pkgs/build-support/docker/stream_layered_image.py')
-rw-r--r-- | pkgs/build-support/docker/stream_layered_image.py | 94 |
1 files changed, 87 insertions, 7 deletions
diff --git a/pkgs/build-support/docker/stream_layered_image.py b/pkgs/build-support/docker/stream_layered_image.py index 60d67442c169b..3e5781ba1c806 100644 --- a/pkgs/build-support/docker/stream_layered_image.py +++ b/pkgs/build-support/docker/stream_layered_image.py @@ -33,6 +33,7 @@ function does all this. import io import os +import re import sys import json import hashlib @@ -126,10 +127,85 @@ class ExtractChecksum: return (self._digest.hexdigest(), self._size) +FromImage = namedtuple("FromImage", ["tar", "manifest_json", "image_json"]) # Some metadata for a layer LayerInfo = namedtuple("LayerInfo", ["size", "checksum", "path", "paths"]) +def load_from_image(from_image_str): + """ + Loads the given base image, if any. + + from_image_str: Path to the base image archive. + + Returns: A 'FromImage' object with references to the loaded base image, + or 'None' if no base image was provided. + """ + if from_image_str is None: + return None + + base_tar = tarfile.open(from_image_str) + + manifest_json_tarinfo = base_tar.getmember("manifest.json") + with base_tar.extractfile(manifest_json_tarinfo) as f: + manifest_json = json.load(f) + + image_json_tarinfo = base_tar.getmember(manifest_json[0]["Config"]) + with base_tar.extractfile(image_json_tarinfo) as f: + image_json = json.load(f) + + return FromImage(base_tar, manifest_json, image_json) + + +def add_base_layers(tar, from_image): + """ + Adds the layers from the given base image to the final image. + + tar: 'tarfile.TarFile' object for new layers to be added to. + from_image: 'FromImage' object with references to the loaded base image. + """ + if from_image is None: + print("No 'fromImage' provided", file=sys.stderr) + return [] + + layers = from_image.manifest_json[0]["Layers"] + checksums = from_image.image_json["rootfs"]["diff_ids"] + layers_checksums = zip(layers, checksums) + + for num, (layer, checksum) in enumerate(layers_checksums, start=1): + layer_tarinfo = from_image.tar.getmember(layer) + checksum = re.sub(r"^sha256:", "", checksum) + + tar.addfile(layer_tarinfo, from_image.tar.extractfile(layer_tarinfo)) + path = layer_tarinfo.path + size = layer_tarinfo.size + + print("Adding base layer", num, "from", path, file=sys.stderr) + yield LayerInfo(size=size, checksum=checksum, path=path, paths=[path]) + + from_image.tar.close() + + +def overlay_base_config(from_image, final_config): + """ + Overlays the final image 'config' JSON on top of selected defaults from the + base image 'config' JSON. + + from_image: 'FromImage' object with references to the loaded base image. + final_config: 'dict' object of the final image 'config' JSON. + """ + if from_image is None: + return final_config + + base_config = from_image.image_json["config"] + + # Preserve environment from base image + final_env = base_config.get("Env", []) + final_config.get("Env", []) + if final_env: + final_config["Env"] = final_env + return final_config + + def add_layer_dir(tar, paths, store_dir, mtime): """ Appends given store paths to a TarFile object as a new layer. @@ -248,17 +324,21 @@ def main(): mtime = int(created.timestamp()) store_dir = conf["store_dir"] + from_image = load_from_image(conf["from_image"]) + with tarfile.open(mode="w|", fileobj=sys.stdout.buffer) as tar: layers = [] - for num, store_layer in enumerate(conf["store_layers"]): - print( - "Creating layer", num, - "from paths:", store_layer, - file=sys.stderr) + layers.extend(add_base_layers(tar, from_image)) + + start = len(layers) + 1 + for num, store_layer in enumerate(conf["store_layers"], start=start): + print("Creating layer", num, "from paths:", store_layer, + file=sys.stderr) info = add_layer_dir(tar, store_layer, store_dir, mtime=mtime) layers.append(info) - print("Creating the customisation layer...", file=sys.stderr) + print("Creating layer", len(layers) + 1, "with customisation...", + file=sys.stderr) layers.append( add_customisation_layer( tar, @@ -273,7 +353,7 @@ def main(): "created": datetime.isoformat(created), "architecture": conf["architecture"], "os": "linux", - "config": conf["config"], + "config": overlay_base_config(from_image, conf["config"]), "rootfs": { "diff_ids": [f"sha256:{layer.checksum}" for layer in layers], "type": "layers", |