#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import argparse import json import os import shutil import sys from six.moves import configparser import kobo.conf import pungi.checks import pungi.util from pungi_utils import config_utils def load_file(source, conf): try: with open(source) as f: conf.load_from_dict(json.load(f)) except ValueError: conf.load_from_file(source) def load_source(source, conf): if os.path.isfile(source): load_file(source, conf) else: load_file(os.path.join(source, "logs/global/config-dump.global.log"), conf) def dump_multi_config(conf_file, dest, **kwargs): """Given a multi compose config, clone it and all referenced files to a given directory. """ parser = configparser.RawConfigParser() parser.read(conf_file) pungi.util.makedirs(dest) basedir = os.path.dirname(conf_file) def copy_local_files(config): """Helper function called on all loaded Pungi configs that copies local variants or comps files (unless specified by absolute path). """ for opt in "comps_file", "variants_file": val = config.get(opt) if isinstance(val, str) and val[0] != "/": shutil.copy2(os.path.join(basedir, val), dest) for section in parser.sections(): if section == "general": continue file_path = parser.get(section, "config") dest_file = os.path.splitext(os.path.join(dest, file_path))[0] + ".json" with open(dest_file, "w") as fh: if not process_file( [os.path.join(basedir, file_path)], out=fh, callback=copy_local_files, **kwargs ): return False parser.set(section, "config", os.path.basename(dest_file)) with open(os.path.join(dest, os.path.basename(conf_file)), "w") as fh: parser.write(fh) return True def process_file( sources, out=sys.stdout, callback=None, defines=None, just_dump=False, event=None, offline=False, ): """Load Pungi config file, validate it, optionally resolve Git references, and dump created JSON to a given stream. :param callable callback: a callable to call with parsed config :param dict defines: mapping of values to define before parsing the config :param bool just_dump: skip validation and adding default values :param int event: Koji event to hardcode into the config :param bool offline: skip resolving Git references :returns: False if validation failed, True otherwise """ conf = kobo.conf.PyConfigParser() conf.load_from_dict(defines or {}) for source in sources: load_source(source, conf) if not just_dump: errors, _ = pungi.checks.validate(conf, offline=offline) if errors: for error in errors: print(error, file=sys.stderr) return False if event: conf["koji_event"] = event # Clean up defines from the final final config. We don't want to keep them # as they would cause warnings during validation. for key in defines: del conf[key] if callback: callback(conf) json.dump(conf, out, sort_keys=True, indent=4) return True def main(): parser = argparse.ArgumentParser() parser.add_argument( "sources", metavar="SOURCE", nargs="+", help=( "Source for the configuration; either a compose " "or arbitrary number of config files." ), ) parser.add_argument( "--freeze-event", metavar="ID", type=pungi.util.parse_koji_event, help=( "Include this koji event in the created config; " "takes either event ID or path to a compose" ), ) parser.add_argument( "-e", "--define", action="append", default=[], metavar="VAR=VALUE", type=config_utils.validate_definition, help=( "Define a variable on command line and inject it into the config file. " "Can be used multiple times." ), ) group = parser.add_mutually_exclusive_group() group.add_argument( "--just-dump", action="store_true", help=( "Do not transform the config in any way. Default values are not " "added, git references are not resolved." ), ) group.add_argument( "--offline", action="store_true", help="Do not resolve git references." ) parser.add_argument( "--multi", metavar="DIR", help=( "Treat source as config for pungi-orchestrate and store dump into " "given directory." ), ) args = parser.parse_args() defines = config_utils.extract_defines(args.define) if args.multi: if len(args.sources) > 1: parser.error("Only one multi config can be specified.") return dump_multi_config( args.sources[0], dest=args.multi, defines=defines, just_dump=args.just_dump, event=args.freeze_event, offline=args.offline, ) return process_file( args.sources, defines=defines, just_dump=args.just_dump, event=args.freeze_event, offline=args.offline, ) if __name__ == "__main__": if not main(): sys.exit(1)