Blob Blame History Raw
#!/usr/bin/python3
"""
Copy files from a source to the tree

Copies files obtained via a `source` to the tree. Multiple files or
directories can be copied by specifying multiple entries in `paths`.
If no paths are specified the whole contents of `source` is copied.
The source and the target path for each individual path entry might
optionally be specified via `from` and `to`, respectively; if no
path is given for any of the two, the root `/` is assumed.

Supported sources are currently:

 o `archive`: Fetch an archive via the org.osbuild.files source and
   extract it to a temporary directory. Currently only tar archives
   are supported.

"""


import json
import os
import sys
import subprocess
import tempfile

import osbuild.sources


SCHEMA = """
"additionalProperties": false,
"definitions": {
  "source-archive": {
    "description": "Fetch an via org.osbuild.files and extract it",
    "type": "object",
    "required": ["type", "checksum"],
    "additionalProperties": false,
    "properties": {
      "type": {
        "type": "string",
         "description": "The type of the source, here 'archive'",
         "enum": ["archive"]
      },
      "checksum": {
        "type": "string",
        "description": "The checksum of the archive to fetch"
      },
      "strip-components": {
        "type": "number",
        "description": "Strip 'N' leading components from file names on extraction",
        "default": 1
      }
    }
  }
},
"properties": {
  "source": {
    "oneOf": [{ "$ref": "#/definitions/source-archive" }]
  },
  "paths": {
    "description": "Array of items to copy",
    "type": "array",
    "items": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "from": {
          "type": "string",
          "description": "The source path",
          "default": "/"
        },
        "to": {
          "type": "string",
          "description": "The destination path",
          "default": "/"
        }
      }
    }
  }
}
"""


def get_archive(options, srcdir, workdir):
    checksum = options["checksum"]
    strip = options.get("strip-components", 1)
    source_path = os.path.join(srcdir, "org.osbuild.files")

    target = os.path.join(workdir, checksum)
    os.makedirs(target)

    # actually fetch the archive
    res = osbuild.sources.get("org.osbuild.files", [checksum])

    tar = [
        "tar",
        "--strip-components=" + str(strip),
        "-x",
        "-C", target,
        "-f", os.path.join(source_path, checksum)
    ]

    subprocess.run(tar, check=True)

    return target, res


def main(tree, srcdir, options, workdir):
    source_options = options["source"]
    source_type = source_options["type"]
    paths = options.get("paths", [{}])

    if source_type == "archive":
        source = get_archive
    else:
        raise ValueError(f"Unsupported source: {source_type}")

    # Fetch the requested data via the selected source
    source_path, _ = source(source_options, srcdir, workdir)

    for path in paths:
        src = path.get("from", "/")
        dst = path.get("to", "/")

        subprocess.run(["cp", "--reflink=auto", "-a",
                        f"{source_path}{src}",
                        f"{tree}{dst}"],
                       check=True)


if __name__ == '__main__':
    stage_args = json.load(sys.stdin)

    with tempfile.TemporaryDirectory(dir="/var/tmp") as _workdir:
        r = main(stage_args["tree"],
                 stage_args["sources"],
                 stage_args["options"],
                 _workdir)

        sys.exit(r)