Blame SOURCES/ansible_collection.py

rdobuilder 6b0be9
#!/usr/bin/python3
rdobuilder 6b0be9
# SPDX-License-Identifier: GPL-3.0-or-later
rdobuilder 6b0be9
# SPDX-FileCopyrightText 2022 Maxwell G <gotmax@e.email>
rdobuilder 6b0be9
rdobuilder 6b0be9
"""
rdobuilder 6b0be9
This script uses Ansible Collection metadata from galaxy.yml to figure out the
rdobuilder 6b0be9
namespace, name, and version of the collection being packaged.
rdobuilder 6b0be9
rdobuilder 6b0be9
``ansible_collection.py install`` (used by %ansible_collecton_install) uses
rdobuilder 6b0be9
this information to find and install the collection artifact that was just
rdobuilder 6b0be9
built with %ansible_collection_build. It also generates a files list for use
rdobuilder 6b0be9
with `%files -f`.
rdobuilder 6b0be9
rdobuilder 6b0be9
``ansible_collection.py test`` (used by %ansible_test_unit) parses galaxy.yml
rdobuilder 6b0be9
to determine the collection namespace and name that's needed to create the
rdobuilder 6b0be9
directory structure that ansible-test expects. After creating a temporary build
rdobuilder 6b0be9
directory with the needed structure, the script runs ansible-test units with
rdobuilder 6b0be9
the provided arguments.
rdobuilder 6b0be9
"""
rdobuilder 6b0be9
rdobuilder 6b0be9
import argparse
rdobuilder 6b0be9
import shutil
rdobuilder 6b0be9
import subprocess
rdobuilder 6b0be9
import sys
rdobuilder 6b0be9
from pathlib import Path
rdobuilder 6b0be9
from tempfile import TemporaryDirectory
rdobuilder 6b0be9
from typing import Any, Dict, Optional, Sequence, Union
rdobuilder 6b0be9
rdobuilder 6b0be9
from yaml import CSafeLoader, load
rdobuilder 6b0be9
rdobuilder 6b0be9
rdobuilder 6b0be9
class CollectionError(Exception):
rdobuilder 6b0be9
    pass
rdobuilder 6b0be9
rdobuilder 6b0be9
rdobuilder 6b0be9
class AnsibleCollection:
rdobuilder 6b0be9
    def __init__(self, collection_srcdir: Optional[Path] = None) -> None:
rdobuilder 6b0be9
        self.collection_srcdir = collection_srcdir or Path.cwd()
rdobuilder 6b0be9
        self.data = self._load_data()
rdobuilder 6b0be9
        self.namespace = self.data["namespace"]
rdobuilder 6b0be9
        self.name = self.data["name"]
rdobuilder 6b0be9
        self.version = self.data["version"]
rdobuilder 6b0be9
rdobuilder 6b0be9
    def _load_data(self) -> Dict[str, Any]:
rdobuilder 6b0be9
        path = self.collection_srcdir / "galaxy.yml"
rdobuilder 6b0be9
        if not path.exists():
rdobuilder 6b0be9
            raise CollectionError(f"{path} does not exist!")
rdobuilder 6b0be9
        print(f"Loading collection metadata from {path}")
rdobuilder 6b0be9
rdobuilder 6b0be9
        with open(path, encoding="utf-8") as file:
rdobuilder 6b0be9
            return load(file, Loader=CSafeLoader)
rdobuilder 6b0be9
rdobuilder 6b0be9
    def install(self, destdir: Union[str, Path]) -> None:
rdobuilder 6b0be9
        artifact = self.collection_srcdir / Path(
rdobuilder 6b0be9
            f"{self.namespace}-{self.name}-{self.version}.tar.gz"
rdobuilder 6b0be9
        )
rdobuilder 6b0be9
        if not artifact.exists() and not artifact.is_file():
rdobuilder 6b0be9
            raise CollectionError(
rdobuilder 6b0be9
                f"{artifact} does not exist! Did you run %ansible_collection_build?"
rdobuilder 6b0be9
            )
rdobuilder 6b0be9
rdobuilder 6b0be9
        args = (
rdobuilder 6b0be9
            "ansible-galaxy",
rdobuilder 6b0be9
            "collection",
rdobuilder 6b0be9
            "install",
rdobuilder 6b0be9
            "-n",
rdobuilder 6b0be9
            "-p",
rdobuilder 6b0be9
            str(destdir),
rdobuilder 6b0be9
            str(artifact),
rdobuilder 6b0be9
        )
rdobuilder 6b0be9
        print(f"Running: {args}")
rdobuilder 6b0be9
        print()
rdobuilder 6b0be9
        # Without this, the print statements are shown after the command
rdobuilder 6b0be9
        # output when building in mock.
rdobuilder 6b0be9
        sys.stdout.flush()
rdobuilder 6b0be9
        subprocess.run(args, check=True, cwd=self.collection_srcdir)
rdobuilder 6b0be9
        print()
rdobuilder 6b0be9
rdobuilder 6b0be9
    def write_filelist(self, filelist: Path) -> None:
rdobuilder 6b0be9
        filelist.parent.mkdir(parents=True, exist_ok=True)
rdobuilder 6b0be9
        contents = "%{ansible_collections_dir}/" + self.namespace
rdobuilder 6b0be9
        print(f"Writing filelist to {filelist}")
rdobuilder 6b0be9
        with open(filelist, "w", encoding="utf-8") as file:
rdobuilder 6b0be9
            file.write(contents)
rdobuilder 6b0be9
rdobuilder 6b0be9
    def unit_test(self, extra_args: Sequence) -> None:
rdobuilder 6b0be9
        with TemporaryDirectory() as temp:
rdobuilder 6b0be9
            temppath = Path(temp) / "ansible_collections" / self.namespace / self.name
rdobuilder 6b0be9
            shutil.copytree(
rdobuilder 6b0be9
                self.collection_srcdir,
rdobuilder 6b0be9
                temppath,
rdobuilder 6b0be9
            )
rdobuilder 6b0be9
            args = ("ansible-test", "units", *extra_args)
rdobuilder 6b0be9
            print(f"Running: {args}")
rdobuilder 6b0be9
            print()
rdobuilder 6b0be9
            # Without this, the print statements are shown after the command
rdobuilder 6b0be9
            # output when building in mock.
rdobuilder 6b0be9
            sys.stdout.flush()
rdobuilder 6b0be9
            subprocess.run(args, cwd=temppath, check=True)
rdobuilder 6b0be9
rdobuilder 6b0be9
rdobuilder 6b0be9
def parseargs() -> argparse.Namespace:
rdobuilder 6b0be9
    parser = argparse.ArgumentParser(
rdobuilder 6b0be9
        "Install and test Ansible Collections in an rpmbuild environment"
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
    subparsers = parser.add_subparsers(dest="action")
rdobuilder 6b0be9
    install_parser = subparsers.add_parser(
rdobuilder 6b0be9
        "install",
rdobuilder 6b0be9
        help="Run ansible-galaxy collection install and write filelist",
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
    install_parser.add_argument(
rdobuilder 6b0be9
        "--collections-dir",
rdobuilder 6b0be9
        required=True,
rdobuilder 6b0be9
        help="Collection destination directory",
rdobuilder 6b0be9
        type=Path,
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
    install_parser.add_argument(
rdobuilder 6b0be9
        "--filelist",
rdobuilder 6b0be9
        type=Path,
rdobuilder 6b0be9
        required=True,
rdobuilder 6b0be9
        help="%%{ansible_collection_filelist}",
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
rdobuilder 6b0be9
    test_parser = subparsers.add_parser(
rdobuilder 6b0be9
        "test",
rdobuilder 6b0be9
        help="Run ansible-test unit after creating the necessary directory structure",
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
    test_parser.add_argument(
rdobuilder 6b0be9
        "extra_args", nargs="*", help="Extra arguments to pass to ansible-test"
rdobuilder 6b0be9
    )
rdobuilder 6b0be9
    args = parser.parse_args()
rdobuilder 6b0be9
    # add_subparsers does not support required on Python 3.6
rdobuilder 6b0be9
    if not args.action:
rdobuilder 6b0be9
        parser.print_usage()
rdobuilder 6b0be9
        sys.exit(2)
rdobuilder 6b0be9
    return args
rdobuilder 6b0be9
rdobuilder 6b0be9
rdobuilder 6b0be9
def main():
rdobuilder 6b0be9
    args = parseargs()
rdobuilder 6b0be9
    collection = AnsibleCollection()
rdobuilder 6b0be9
    if args.action == "install":
rdobuilder 6b0be9
        collection.install(args.collections_dir)
rdobuilder 6b0be9
        collection.write_filelist(args.filelist)
rdobuilder 6b0be9
    elif args.action == "test":
rdobuilder 6b0be9
        collection.unit_test(args.extra_args)
rdobuilder 6b0be9
rdobuilder 6b0be9
rdobuilder 6b0be9
if __name__ == "__main__":
rdobuilder 6b0be9
    try:
rdobuilder 6b0be9
        main()
rdobuilder 6b0be9
    except (CollectionError, subprocess.CalledProcessError) as err:
rdobuilder 6b0be9
        sys.exit(err)