Skip to content

Commit 2299247

Browse files
eds-collaboramgalka
authored andcommitted
Allow configuration to be loaded from multiple sources
This means that a user of this toolset can add their own configuration as a separate file, which overrides the supplied defaults as necessary. One use is to provide the configuration for an internal lab, or internal kernels. This adds an `--extra-config` parameter in addition to the existing `--yaml-config` default configuration. It's an extra parameter because we want to keep the implicit default configuration in these cases, rather than have to specify a path to it ourselves (which might drift, for example, and in any case is less usable). Signed-off-by: Ed Smith <[email protected]>
1 parent 9cfa7c2 commit 2299247

File tree

7 files changed

+104
-11
lines changed

7 files changed

+104
-11
lines changed

kci_bisect

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ class cmd_get_mbox(Command):
6262

6363
if __name__ == '__main__':
6464
opts = parse_opts("kci_bisect", globals())
65-
configs = kernelci.config.load(opts.yaml_config)
65+
configs = kernelci.config.load_all(opts.get_yaml_configs())
6666
status = opts.command(configs, opts)
6767
sys.exit(0 if status is True else 1)

kci_build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,6 @@ class cmd_push_tarball(Command):
476476

477477
if __name__ == '__main__':
478478
opts = parse_opts("kci_build", globals())
479-
configs = kernelci.config.load(opts.yaml_config)
479+
configs = kernelci.config.load_all(opts.get_yaml_configs())
480480
status = opts.command(configs, opts)
481481
sys.exit(0 if status is True else 1)

kci_data

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,6 @@ class cmd_create_user(Command):
115115

116116
if __name__ == '__main__':
117117
opts = parse_opts("kci_data", globals())
118-
configs = kernelci.config.load(opts.yaml_config)
118+
configs = kernelci.config.load_all(opts.get_yaml_configs())
119119
status = opts.command(configs, opts)
120120
sys.exit(0 if status is True else 1)

kci_rootfs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,6 @@ class cmd_upload(Command):
145145

146146
if __name__ == '__main__':
147147
opts = parse_opts("kci_rootfs", globals())
148-
configs = kernelci.config.load(opts.yaml_config)
148+
configs = kernelci.config.load_all(opts.get_yaml_configs())
149149
status = opts.command(configs, opts)
150150
sys.exit(0 if status is True else 1)

kci_test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,6 @@ class cmd_submit(Command):
390390

391391
if __name__ == '__main__':
392392
opts = parse_opts("kci_test", globals())
393-
configs = kernelci.config.load(opts.yaml_config)
393+
configs = kernelci.config.load_all(opts.get_yaml_configs())
394394
status = opts.command(configs, opts)
395395
sys.exit(0 if status is True else 1)

kernelci/cli.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,10 @@ def get_missing_args(self):
523523
missing_args.append(arg_name)
524524
return missing_args
525525

526+
def get_yaml_configs(self):
527+
"""Get a list of all the YAML configuration files."""
528+
return [self._cli_args.yaml_config] + self._cli_args.extra_config
529+
526530

527531
def make_parser(title, default_config_path):
528532
"""Helper to make a parser object from argparse.
@@ -531,10 +535,21 @@ def make_parser(title, default_config_path):
531535
*default_config_path* is the default YAML config directory to use
532536
"""
533537
parser = argparse.ArgumentParser(title)
534-
parser.add_argument("--yaml-config", default=default_config_path,
535-
help="Path to the directory with YAML config files")
536-
parser.add_argument("--settings",
537-
help="Path to the settings file")
538+
parser.add_argument(
539+
"--yaml-config",
540+
default=default_config_path,
541+
help="Path to the Kernel CI directory with YAML config files",
542+
)
543+
parser.add_argument(
544+
"--extra-config",
545+
action='append',
546+
default=list(),
547+
help="Path to additional YAML site config files",
548+
)
549+
parser.add_argument(
550+
"--settings",
551+
help="Path to the settings file",
552+
)
538553
return parser
539554

540555

kernelci/config/__init__.py

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ def load_yaml(config_path, validate_entries=None):
6464
will be merged together under the same top-level dictionary key.
6565
6666
*config_path* is the path to the YAML config directory, or alternative a
67-
single YAML file
67+
single YAML file.
68+
69+
*validate_entries* is currently unused.
6870
"""
6971
config = dict()
7072
for yaml_path, data in _iterate_yaml_files(config_path):
@@ -79,6 +81,64 @@ def load_yaml(config_path, validate_entries=None):
7981
return config
8082

8183

84+
def _merge_trees(old, update):
85+
"""Merge two values loaded from YAML
86+
87+
This combines two values recursively that have been loaded from
88+
YAML. The data from *update* will be overlaid onto *old*
89+
according to the rules:
90+
91+
- If *old* and *update* are dictionaries, their keys will be
92+
unified, with the values for any keys present in both
93+
dictionaries being merged recursively.
94+
95+
- If *old* and *update* are lists, the result is the concatenation
96+
of the two lists.
97+
98+
- Otherwise, *update* replaces *old*.
99+
100+
Neither *old* nor *update* is modified; any modifications required
101+
lead to a new value being returned.
102+
103+
"""
104+
if isinstance(old, dict) and isinstance(update, dict):
105+
res = dict()
106+
for k in (set(old) | set(update)):
107+
if (k in old) and (k in update):
108+
res[k] = _merge_trees(old[k], update[k])
109+
elif k in old:
110+
res[k] = old[k]
111+
else:
112+
res[k] = update[k]
113+
return res
114+
elif isinstance(old, list) and isinstance(update, list):
115+
return old + update
116+
else:
117+
return update
118+
119+
120+
def load_all_yaml(config_paths, validate_entries=None):
121+
"""Load the YAML configuration
122+
123+
Load all the YAML files in all the specific configuration
124+
directories and aggregate them together. Later directories take
125+
precedence over earlier ones. This enables combining sources of
126+
configuration data from multiple places.
127+
128+
*config_paths* is an ordered list of YAML configuration
129+
directories or YAML files, with later entries
130+
having higher priority.
131+
132+
*validate_entries* is passed to `load_yaml()`
133+
134+
"""
135+
config = dict()
136+
for path in config_paths:
137+
data = load_yaml(path, validate_entries)
138+
config = _merge_trees(config, data)
139+
return config
140+
141+
82142
def from_data(data):
83143
"""Create configuration objects from the YAML data
84144
@@ -104,9 +164,27 @@ def load(config_path):
104164
Load all the YAML files found in the configuration directory then create
105165
a dictionary containing the configuration objects and return it.
106166
107-
*config_path* is the path to the YAML config directory
167+
*config_path* is the path to the YAML config directory or a
168+
unified file
169+
108170
"""
109171
if config_path is None:
110172
return {}
111173
data = load_yaml(config_path)
112174
return from_data(data)
175+
176+
177+
def load_all(config_paths):
178+
"""Load the configuration from YAML files
179+
180+
Load all the YAML files found in all the configuration
181+
directories, then create a dictionary containing the configuration
182+
objects and return it. Note that the config paths are in priority
183+
order, with later entries overriding earlier ones.
184+
185+
*config_paths* is a list of YAML config directories (or unified
186+
files)
187+
188+
"""
189+
data = load_all_yaml(config_paths)
190+
return from_data(data)

0 commit comments

Comments
 (0)