Skip to content

Commit 026f4b5

Browse files
committed
jobs.py: read the config like pytest and tools.py
Make jobs.py use the same configuration as pytest and tools.py: -c/--config, XCPNG_CONFIG/XCPNG_CONFIG_DIR and --config-value are now accepted, and jobs.py run falls back to the hosts defined in the config's [hosts] section when no hosts are given explicitly. Documented in the README. Signed-off-by: Gaëtan Lehmann <gaetan.lehmann@vates.tech>
1 parent f219dd2 commit 026f4b5

4 files changed

Lines changed: 83 additions & 48 deletions

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,15 +418,18 @@ pytest tests/uefi_sb -m "multi_vms and unix_vm" --hosts=ip_of_poolmaster --vm=ht
418418

419419
#### Run a job
420420
```
421-
usage: jobs.py run [-h] [--print-only] job hosts ...
421+
usage: jobs.py run [-h] [-c PATH] [--config-value KEY=VALUE] [--print-only] job [hosts] ...
422422
423423
positional arguments:
424424
job name of the job to run.
425425
hosts master host(s) of pools to run the tests on, comma-separated.
426+
When omitted, the hosts from the config's [hosts] section are used.
426427
pytest_args all additional arguments after the last positional argument will be passed to pytest and replace default job params if needed.
427428
428429
optional arguments:
429430
-h, --help show this help message and exit
431+
-c PATH, --config PATH config overlay: a .toml file path or profile name
432+
--config-value KEY=VALUE override a config value (repeatable; highest priority)
430433
--print-only, -p print the command, but don't run it. Must be specified before positional arguments.
431434
```
432435

@@ -440,6 +443,13 @@ pytest tests/uefi_sb -m "multi_vms and unix_vm" --hosts=ip_of_poolmaster --vm=ht
440443

441444
Any parameter added at the end of the command will be passed to `pytest`. Any parameter added that is already defined in the job's "params" (see output of `./jobs.py show`) will replace it, and `--vm` also replaces `--vm[]` in the case of jobs designed to run tests on multiple VMs.
442445

446+
`jobs.py` reads the same configuration as `pytest` and `tools.py`:
447+
`-c`/`--config`, the `XCPNG_CONFIG`/`XCPNG_CONFIG_DIR` env vars and
448+
`--config-value` are supported, and when no hosts are given, `jobs.py run`
449+
falls back to the hosts defined in the config's `[hosts]` section. The
450+
config options must be specified before the positional `job`/`hosts`
451+
arguments (like `--print-only`), otherwise they are forwarded to pytest.
452+
443453
```
444454
# same, but we override the list of VMs
445455
$ ./jobs.py run --print-only sb-unix-multi ip_of_poolmaster --vm=http://path/to/vm4.xva

jobs.py

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88

99
from lib.commands import ssh
10+
from lib.config_loader import add_config_options, load_config
1011

1112
from typing import NotRequired, TypedDict, cast
1213

@@ -815,14 +816,30 @@ def extract_tests(cmd: list[str]) -> set[str]:
815816
if error:
816817
sys.exit(1)
817818

819+
def _config_hosts(args: argparse.Namespace) -> str | None:
820+
"""Return the pool masters listed in the config's [hosts], as a comma-separated string, or None."""
821+
cfg = load_config(override=args.config, config_values=args.config_value)
822+
hosts = list(cfg.hosts.keys())
823+
return ",".join(hosts) if hosts else None
824+
825+
818826
def action_run(args: argparse.Namespace) -> None:
819-
cmd = build_pytest_cmd(JOBS[args.job], args.hosts, None, args.pytest_args)
827+
hosts = args.hosts or _config_hosts(args)
828+
if hosts is None:
829+
print("Error: no hosts provided. Pass a comma-separated list of pool masters as the positional "
830+
"hosts argument, or define them in the [hosts] section of the config.", file=sys.stderr)
831+
sys.exit(1)
832+
cmd = build_pytest_cmd(JOBS[args.job], hosts, None, args.pytest_args)
833+
if args.config is not None:
834+
cmd += ["--config", str(args.config)]
835+
for config_value in args.config_value:
836+
cmd += ["--config-value", config_value]
820837
print(subprocess.list2cmdline(cmd))
821838
if args.print_only:
822839
return
823840

824841
# check that enough pool masters have been provided
825-
nb_pools = len(args.hosts.split(","))
842+
nb_pools = len(hosts.split(","))
826843
job_nb_pools = JOBS[args.job]["nb_pools"]
827844
assert isinstance(job_nb_pools, int)
828845
if nb_pools < job_nb_pools:
@@ -841,35 +858,43 @@ def action_run(args: argparse.Namespace) -> None:
841858

842859
def main() -> None:
843860
parser = argparse.ArgumentParser(description="Manage test jobs")
861+
common_parser = add_config_options(parser)
862+
844863
subparsers = parser.add_subparsers(dest="action", metavar="action")
845864
subparsers.required = True
846865

847-
list_parser = subparsers.add_parser("list", help="list available jobs.")
866+
list_parser = subparsers.add_parser("list", help="list available jobs.", parents=[common_parser])
848867
list_parser.set_defaults(func=action_list)
849868

850-
run_parser = subparsers.add_parser("show", help="show details about a job definition.")
851-
run_parser.add_argument("job", help="name of the job.", choices=JOBS.keys(), metavar="job")
852-
run_parser.set_defaults(func=action_show)
869+
show_parser = subparsers.add_parser("show", help="show details about a job definition.", parents=[common_parser])
870+
show_parser.add_argument("job", help="name of the job.", choices=JOBS.keys(), metavar="job")
871+
show_parser.set_defaults(func=action_show)
853872

854-
run_parser = subparsers.add_parser("collect", help="show test collection based on the job definition.")
855-
run_parser.add_argument("job", help="name of the job.", choices=JOBS.keys(), metavar="job")
856-
run_parser.add_argument("-v", "--host-version", help="host version to match VM filters.")
857-
run_parser.add_argument("pytest_args", nargs=argparse.REMAINDER,
858-
help="all additional arguments after the last positional argument will "
859-
"be passed to pytest and replace default job params if needed.")
860-
run_parser.set_defaults(func=action_collect)
873+
collect_parser = subparsers.add_parser("collect", help="show test collection based on the job definition.",
874+
parents=[common_parser])
875+
collect_parser.add_argument("job", help="name of the job.", choices=JOBS.keys(), metavar="job")
876+
collect_parser.add_argument("-v", "--host-version", help="host version to match VM filters.")
877+
collect_parser.add_argument("pytest_args", nargs=argparse.REMAINDER,
878+
help="all additional arguments after the last positional argument will "
879+
"be passed to pytest and replace default job params if needed.")
880+
collect_parser.set_defaults(func=action_collect)
861881

862-
run_parser = subparsers.add_parser("check", help="run sanity checks on the tests and jobs.")
863-
run_parser.set_defaults(func=action_check)
882+
check_parser = subparsers.add_parser(
883+
"check", help="run sanity checks on the tests and jobs.", parents=[common_parser])
884+
check_parser.set_defaults(func=action_check)
864885

865-
run_parser = subparsers.add_parser("run", help="run a job.")
886+
run_parser = subparsers.add_parser("run", help="run a job.", parents=[common_parser])
866887
run_parser.add_argument("--print-only", "-p", action="store_true",
867888
help="print the command, but don't run it. Must be specified before positional arguments.")
868889
run_parser.add_argument("job", help="name of the job to run.", choices=JOBS.keys(), metavar="job")
869-
run_parser.add_argument("hosts", help="master host(s) of pools to run the tests on, comma-separated.")
890+
run_parser.add_argument("hosts", nargs="?", default=None,
891+
help="master host(s) of pools to run the tests on, comma-separated. When omitted, the "
892+
"hosts from the config's [hosts] section are used.")
870893
run_parser.add_argument("pytest_args", nargs=argparse.REMAINDER,
871894
help="all additional arguments after the last positional argument will "
872-
"be passed to pytest and replace default job params if needed.")
895+
"be passed to pytest and replace default job params if needed. "
896+
"Note: -c/--config and --config-value must be specified before the "
897+
"positional arguments.")
873898
run_parser.set_defaults(func=action_run)
874899

875900
args = parser.parse_args()

lib/config_loader.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import argparse
34
import os
45
import tomllib
56
import warnings
@@ -540,6 +541,32 @@ def apply_override(config_name: str | None = None, config_values: list[str] | No
540541
setattr(config, field, getattr(new, field))
541542

542543

544+
def add_config_options(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
545+
"""Add -c/--config and --config-value to `parser`, and return a parent parser for subcommands.
546+
547+
Both options are attached to `parser` (so they can be given before the
548+
subcommand) and to a shared ``add_help=False`` parser that subcommands use
549+
as a parent.
550+
"""
551+
common_parser = argparse.ArgumentParser(add_help=False)
552+
for target in (parser, common_parser):
553+
target.add_argument(
554+
"-c", "--config",
555+
type=Path,
556+
default=None,
557+
metavar="PATH",
558+
help="Config overlay: a .toml file path or profile name (default: config.default.toml or XCPNG_CONFIG)",
559+
)
560+
target.add_argument(
561+
"--config-value",
562+
action="append",
563+
default=[],
564+
metavar="KEY=VALUE",
565+
help="Override a config value, e.g. host.default_password=foo (repeatable; highest priority)",
566+
)
567+
return common_parser
568+
569+
543570
def sr_device_config(
544571
config_key: str, *, required: list[str] | None = None
545572
) -> dict[str, str]:

lib/tools/cli.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
import logging
99
import os
1010
import sys
11-
from pathlib import Path
1211

1312
from lib.common import HostAddress
14-
from lib.config_loader import base_config_dict, load_config
13+
from lib.config_loader import add_config_options, base_config_dict, load_config
1514
from lib.tools import logger
1615
from lib.tools.inventory import into_inventory, inventory_from_config
1716
from lib.tools.tasks.clean import clean_pools
@@ -101,33 +100,7 @@ def cli() -> None:
101100
description="Tools that help developers for running recurrent tasks on their XCP-ng sandbox."
102101
)
103102
parser.add_argument("-d", "--debug", action="store_true", default=False, help="Enable debug level")
104-
parser.add_argument(
105-
"-c", "--config",
106-
type=Path,
107-
default=None,
108-
metavar="PATH",
109-
help="Config overlay: a .toml file path or profile name (default: config.default.toml or XCPNG_CONFIG)",
110-
)
111-
common_parser = argparse.ArgumentParser(add_help=False)
112-
common_parser.add_argument(
113-
"-c", "--config",
114-
type=Path,
115-
default=None,
116-
metavar="PATH",
117-
help="Config overlay: a .toml file path or profile name (default: config.default.toml or XCPNG_CONFIG)",
118-
)
119-
120-
def _add_config_value_option(target: argparse.ArgumentParser) -> None:
121-
target.add_argument(
122-
"--config-value",
123-
action="append",
124-
default=[],
125-
metavar="KEY=VALUE",
126-
help="Override a config value, e.g. host.default_password=foo (repeatable; highest priority)",
127-
)
128-
129-
_add_config_value_option(parser)
130-
_add_config_value_option(common_parser)
103+
common_parser = add_config_options(parser)
131104

132105
subparsers = parser.add_subparsers(required=True, metavar="COMMAND")
133106

0 commit comments

Comments
 (0)