|
Packit Service |
863627 |
#!/usr/bin/python3
|
|
Packit Service |
863627 |
"""
|
|
Packit Service |
863627 |
Assemble an OCI image archive
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
Assemble an Open Container Initiative[1] image[2] archive, i.e. a
|
|
Packit Service |
863627 |
tarball whose contents is in the OCI image layout.
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
Currently the only required options are `filename` and `architecture`.
|
|
Packit Service |
863627 |
The execution parameters for the image, which then should form the base
|
|
Packit Service |
863627 |
for the container, can be given via `config`. They have the same format
|
|
Packit Service |
863627 |
as the `config` option for the "OCI Image Configuration" (see [2]),
|
|
Packit Service |
863627 |
except those that map to the "Go type map[string]struct{}", which are
|
|
Packit Service |
863627 |
represented as array of strings.
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
The final resulting tarball, aka a "orci-archive", can be imported via
|
|
Packit Service |
863627 |
podman[3] with `podman pull oci-archive:<archive>`.
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
[1] https://www.opencontainers.org/
|
|
Packit Service |
863627 |
[2] https://github.com/opencontainers/image-spec/
|
|
Packit Service |
863627 |
[3] https://podman.io/
|
|
Packit Service |
863627 |
"""
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
import datetime
|
|
Packit Service |
863627 |
import json
|
|
Packit Service |
863627 |
import os
|
|
Packit Service |
863627 |
import subprocess
|
|
Packit Service |
863627 |
import sys
|
|
Packit Service |
863627 |
import tempfile
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
DEFAULT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
SCHEMA = """
|
|
Packit Service |
863627 |
"additionalProperties": false,
|
|
Packit Service |
863627 |
"required": ["architecture", "filename"],
|
|
Packit Service |
863627 |
"properties": {
|
|
Packit Service |
863627 |
"architecture": {
|
|
Packit Service |
863627 |
"description": "The CPU architecture of the image",
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"filename": {
|
|
Packit Service |
863627 |
"description": "Resulting image filename",
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"config": {
|
|
Packit Service |
863627 |
"description": "The execution parameters",
|
|
Packit Service |
863627 |
"type": "object",
|
|
Packit Service |
863627 |
"additionalProperties": false,
|
|
Packit Service |
863627 |
"properties": {
|
|
Packit Service |
863627 |
"Cmd": {
|
|
Packit Service |
863627 |
"type": "array",
|
|
Packit Service |
863627 |
"default": ["sh"],
|
|
Packit Service |
863627 |
"items": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"Env": {
|
|
Packit Service |
863627 |
"type": "array",
|
|
Packit Service |
863627 |
"default": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
|
|
Packit Service |
863627 |
"items": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"ExposedPorts": {
|
|
Packit Service |
863627 |
"type": "array",
|
|
Packit Service |
863627 |
"items": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"User": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"Labels": {
|
|
Packit Service |
863627 |
"type": "object",
|
|
Packit Service |
863627 |
"additionalProperties": true
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"StopSiganl": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"Volumes": {
|
|
Packit Service |
863627 |
"type": "array",
|
|
Packit Service |
863627 |
"items": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
},
|
|
Packit Service |
863627 |
"WorkingDir": {
|
|
Packit Service |
863627 |
"type": "string"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
"""
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
MEDIA_TYPES = {
|
|
Packit Service |
863627 |
"layer": "application/vnd.oci.image.layer.v1.tar",
|
|
Packit Service |
863627 |
"manifest": "application/vnd.oci.image.manifest.v1+json",
|
|
Packit Service |
863627 |
"config": "application/vnd.oci.image.config.v1+json"
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def sha256sum(path: str) -> str:
|
|
Packit Service |
863627 |
ret = subprocess.run(["sha256sum", path],
|
|
Packit Service |
863627 |
stdout=subprocess.PIPE,
|
|
Packit Service |
863627 |
encoding="utf-8",
|
|
Packit Service |
863627 |
check=True)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
return ret.stdout.strip().split(" ")[0]
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def blobs_add_file(blobs: str, path: str, mtype: str):
|
|
Packit Service |
863627 |
digest = sha256sum(path)
|
|
Packit Service |
863627 |
size = os.stat(path).st_size
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
os.rename(path, os.path.join(blobs, digest))
|
|
Packit Service |
863627 |
info = {
|
|
Packit Service |
863627 |
"digest": "sha256:" + digest,
|
|
Packit Service |
863627 |
"size": size,
|
|
Packit Service |
863627 |
"mediaType": MEDIA_TYPES[mtype]
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
print(f"blobs: +{mtype} ({size}, {digest})")
|
|
Packit Service |
863627 |
return info
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def blobs_add_json(blobs: str, js: str, mtype: str):
|
|
Packit Service |
863627 |
js_file = os.path.join(blobs, "temporary.js")
|
|
Packit Service |
863627 |
with open(js_file, "w") as f:
|
|
Packit Service |
863627 |
json.dump(js, f)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
return blobs_add_file(blobs, js_file, mtype)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def blobs_add_layer(blobs: str, tree: str):
|
|
Packit Service |
863627 |
compression = "gzip"
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
layer_file = os.path.join(blobs, "layer.tar")
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
command = [
|
|
Packit Service |
863627 |
"tar",
|
|
Packit Service |
863627 |
"--selinux",
|
|
Packit Service |
863627 |
"--acls",
|
|
Packit Service |
863627 |
"--xattrs",
|
|
Packit Service |
863627 |
"-cf", layer_file,
|
|
Packit Service |
863627 |
"-C", tree,
|
|
Packit Service |
863627 |
] + os.listdir(tree)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
print("creating layer")
|
|
Packit Service |
863627 |
subprocess.run(command,
|
|
Packit Service |
863627 |
stdout=subprocess.DEVNULL,
|
|
Packit Service |
863627 |
check=True)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
digest = "sha256:" + sha256sum(layer_file)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
print("compressing layer")
|
|
Packit Service |
863627 |
suffix = ".compressed"
|
|
Packit Service |
863627 |
subprocess.run([compression,
|
|
Packit Service |
863627 |
"-S", suffix,
|
|
Packit Service |
863627 |
layer_file],
|
|
Packit Service |
863627 |
stdout=subprocess.DEVNULL,
|
|
Packit Service |
863627 |
check=True)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
layer_file += suffix
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
info = blobs_add_file(blobs, layer_file, "layer")
|
|
Packit Service |
863627 |
info["mediaType"] += compression
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
return digest, info
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def config_from_options(options):
|
|
Packit Service |
863627 |
command = options.get("Cmd", ["sh"])
|
|
Packit Service |
863627 |
env = options.get("Env", ["PATH=" + DEFAULT_PATH])
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
config = {
|
|
Packit Service |
863627 |
"Env": env,
|
|
Packit Service |
863627 |
"Cmd": command
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
for name in ["User", "Labels", "StopSignal", "WorkingDir"]:
|
|
Packit Service |
863627 |
item = options.get(name)
|
|
Packit Service |
863627 |
if item:
|
|
Packit Service |
863627 |
config[name] = item
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
for name in ["ExposedPorts", "Volumes"]:
|
|
Packit Service |
863627 |
item = options.get(name)
|
|
Packit Service |
863627 |
if item:
|
|
Packit Service |
863627 |
config[name] = {x: {} for x in item}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
print(config)
|
|
Packit Service |
863627 |
return config
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def create_oci_dir(tree, output_dir, options):
|
|
Packit Service |
863627 |
architecture = options["architecture"]
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
config = {
|
|
Packit Service |
863627 |
"created": datetime.datetime.utcnow().isoformat() + "Z",
|
|
Packit Service |
863627 |
"architecture": architecture,
|
|
Packit Service |
863627 |
"os": "linux",
|
|
Packit Service |
863627 |
"config": config_from_options(options["config"]),
|
|
Packit Service |
863627 |
"rootfs": {
|
|
Packit Service |
863627 |
"type": "layers",
|
|
Packit Service |
863627 |
"diff_ids": []
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
manifest = {
|
|
Packit Service |
863627 |
"schemaVersion": 2,
|
|
Packit Service |
863627 |
"config": None,
|
|
Packit Service |
863627 |
"layers": []
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
index = {
|
|
Packit Service |
863627 |
"schemaVersion": 2,
|
|
Packit Service |
863627 |
"manifests": []
|
|
Packit Service |
863627 |
}
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
blobs = os.path.join(output_dir, "blobs", "sha256")
|
|
Packit Service |
863627 |
os.makedirs(blobs)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
## layers / rootfs
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
digest, info = blobs_add_layer(blobs, tree)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
config["rootfs"]["diff_ids"] = [digest]
|
|
Packit Service |
863627 |
manifest["layers"].append(info)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
## write config
|
|
Packit Service |
863627 |
info = blobs_add_json(blobs, config, "config")
|
|
Packit Service |
863627 |
manifest["config"] = info
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
# manifest
|
|
Packit Service |
863627 |
info = blobs_add_json(blobs, manifest, "manifest")
|
|
Packit Service |
863627 |
index["manifests"].append(info)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
# index
|
|
Packit Service |
863627 |
print("writing index")
|
|
Packit Service |
863627 |
with open(os.path.join(output_dir, "index.json"), "w") as f:
|
|
Packit Service |
863627 |
json.dump(index, f)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
# oci-layout tag
|
|
Packit Service |
863627 |
with open(os.path.join(output_dir, "oci-layout"), "w") as f:
|
|
Packit Service |
863627 |
json.dump({"imageLayoutVersion": "1.0.0"}, f)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
def main(tree, output_dir, options):
|
|
Packit Service |
863627 |
filename = options["filename"]
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
with tempfile.TemporaryDirectory(dir=output_dir) as tmpdir:
|
|
Packit Service |
863627 |
workdir = os.path.join(tmpdir, "output")
|
|
Packit Service |
863627 |
os.makedirs(workdir)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
create_oci_dir(tree, workdir, options)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
command = [
|
|
Packit Service |
863627 |
"tar",
|
|
Packit Service |
863627 |
"--remove-files",
|
|
Packit Service |
863627 |
"-cf", os.path.join(output_dir, filename),
|
|
Packit Service |
863627 |
f"--directory={workdir}",
|
|
Packit Service |
863627 |
] + os.listdir(workdir)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
print("creating final archive")
|
|
Packit Service |
863627 |
subprocess.run(command,
|
|
Packit Service |
863627 |
stdout=subprocess.DEVNULL,
|
|
Packit Service |
863627 |
check=True)
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
|
|
Packit Service |
863627 |
if __name__ == '__main__':
|
|
Packit Service |
863627 |
args = json.load(sys.stdin)
|
|
Packit Service |
863627 |
r = main(args["tree"], args["output_dir"], args["options"])
|
|
Packit Service |
863627 |
sys.exit(r)
|