2018-11-21 13:58:16 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import json
|
|
|
|
import os
|
2019-05-21 13:25:35 +00:00
|
|
|
import shutil
|
2018-11-21 13:58:16 +00:00
|
|
|
import sys
|
|
|
|
|
2019-05-21 13:25:35 +00:00
|
|
|
from six.moves import configparser
|
|
|
|
|
2018-11-21 13:58:16 +00:00
|
|
|
import kobo.conf
|
|
|
|
import pungi.checks
|
2019-02-26 09:50:24 +00:00
|
|
|
import pungi.util
|
2018-11-21 13:58:16 +00:00
|
|
|
|
2019-06-21 07:44:48 +00:00
|
|
|
from pungi_utils import config_utils
|
|
|
|
|
2018-11-21 13:58:16 +00:00
|
|
|
|
|
|
|
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)
|
2019-06-20 06:39:46 +00:00
|
|
|
elif os.path.isdir(source):
|
2018-11-21 13:58:16 +00:00
|
|
|
load_file(os.path.join(source, "logs/global/config-dump.global.log"), conf)
|
2019-06-20 06:39:46 +00:00
|
|
|
else:
|
|
|
|
raise RuntimeError("Source %s is neither file nor directory." % source)
|
2018-11-21 13:58:16 +00:00
|
|
|
|
|
|
|
|
2019-05-21 13:25:35 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2018-11-21 13:58:16 +00:00
|
|
|
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."
|
|
|
|
),
|
|
|
|
)
|
2019-02-26 09:50:24 +00:00
|
|
|
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"
|
|
|
|
),
|
|
|
|
)
|
2019-03-25 11:53:20 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"-e",
|
|
|
|
"--define",
|
|
|
|
action="append",
|
2019-04-03 11:09:12 +00:00
|
|
|
default=[],
|
2019-03-25 11:53:20 +00:00
|
|
|
metavar="VAR=VALUE",
|
2019-06-21 07:44:48 +00:00
|
|
|
type=config_utils.validate_definition,
|
2019-03-25 11:53:20 +00:00
|
|
|
help=(
|
|
|
|
"Define a variable on command line and inject it into the config file. "
|
|
|
|
"Can be used multiple times."
|
|
|
|
),
|
|
|
|
)
|
2018-11-21 13:58:16 +00:00
|
|
|
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."
|
|
|
|
)
|
2019-05-21 13:25:35 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--multi",
|
|
|
|
metavar="DIR",
|
|
|
|
help=(
|
|
|
|
"Treat source as config for pungi-orchestrate and store dump into "
|
|
|
|
"given directory."
|
|
|
|
),
|
|
|
|
)
|
2018-11-21 13:58:16 +00:00
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
2019-06-21 07:44:48 +00:00
|
|
|
defines = config_utils.extract_defines(args.define)
|
2019-03-25 11:53:20 +00:00
|
|
|
|
2019-05-21 13:25:35 +00:00
|
|
|
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,
|
|
|
|
)
|
2019-03-25 11:53:20 +00:00
|
|
|
|
2019-05-21 13:25:35 +00:00
|
|
|
return process_file(
|
|
|
|
args.sources,
|
|
|
|
defines=defines,
|
|
|
|
just_dump=args.just_dump,
|
|
|
|
event=args.freeze_event,
|
|
|
|
offline=args.offline,
|
|
|
|
)
|
2018-11-21 13:58:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2019-06-20 06:39:46 +00:00
|
|
|
try:
|
|
|
|
if not main():
|
|
|
|
sys.exit(1)
|
|
|
|
except RuntimeError as exc:
|
|
|
|
print("Error", str(exc), file=sys.stderr)
|
|
|
|
sys.exit(2)
|