Skip to content

Commit 8aadd02

Browse files
committed
[reproducer] Replace all reproducer vars with zuul vars
Some variables are missing, especially related to the registry credentials. That complicate the situation, where some keys might not exists in reproducer file. Some of the variables are jinja2 template, which are "translated" by Ansible. Alternative way was to use yq command like: yq eval-all -P 'select(fileIndex == 0) * select(fileIndex == 1)' \ /tmp/reproducer-variables.yml \ /home/zuul/configs/zuul_vars.yaml > merged.yaml The yq is available in latest version available for current python3 version installed on hypervisor. In that case, we can not use the yq tool, but use a python script. Co-authored-by: Claude (AI Assistant) claude@anthropic.com Signed-off-by: Daniel Pawlik <dpawlik@redhat.com>
1 parent 8b3b055 commit 8aadd02

2 files changed

Lines changed: 152 additions & 23 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright Red Hat, Inc.
4+
# All Rights Reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7+
# not use this file except in compliance with the License. You may obtain
8+
# a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15+
# License for the specific language governing permissions and limitations
16+
# under the License.
17+
#
18+
# Merge two YAML files whose root is a mapping: keys from OVERRIDE replace BASE
19+
# recursively for nested mappings; non-dict values (scalars, lists) are
20+
# replaced entirely by OVERRIDE. Requires PyYAML (python3-pyyaml / python3-yaml
21+
# on RPM systems).
22+
23+
from __future__ import annotations
24+
25+
import sys
26+
27+
try:
28+
import yaml
29+
except ImportError:
30+
sys.stderr.write(
31+
"merge_yaml_override.py: PyYAML is required "
32+
"(e.g. dnf install python3-pyyaml or pip install pyyaml)\n"
33+
)
34+
sys.exit(1)
35+
36+
37+
class DoubleQuoteDumper(yaml.SafeDumper):
38+
"""YAML SafeDumper subclass;
39+
emits strings with double quotes when needed for readability."""
40+
41+
pass
42+
43+
44+
def _str_representer(dumper, data):
45+
"""Take a SafeDumper and a str;
46+
return a YAML scalar node (literal | or double-quoted when needed)."""
47+
if "\n" in data:
48+
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
49+
if any(c in data for c in "{}[]%*&!@#"):
50+
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"')
51+
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
52+
53+
54+
DoubleQuoteDumper.add_representer(str, _str_representer)
55+
56+
57+
def deep_merge(base: object, override: object) -> object:
58+
"""Take two parsed values;
59+
return merge where both are dicts (recursive), else override wins."""
60+
if base is None:
61+
return override
62+
if override is None:
63+
return base
64+
if isinstance(base, dict) and isinstance(override, dict):
65+
out = dict(base)
66+
for key, val in override.items():
67+
if key in out:
68+
out[key] = deep_merge(out[key], val)
69+
else:
70+
out[key] = val
71+
return out
72+
return override
73+
74+
75+
def main() -> None:
76+
"""Read CLI paths (two or three args),
77+
merge YAML mappings, write stdout or MERGED_OUT; exit on error."""
78+
argc = len(sys.argv)
79+
if argc not in (3, 4):
80+
sys.stderr.write(
81+
f"usage: {sys.argv[0]} FILE1_BASE.yml FILE2_OVERRIDE.yml [MERGED_OUT.yml]\n"
82+
" Deep-merge two YAML mappings: FILE2 wins on duplicate keys (nested dicts merged).\n"
83+
" Scalars and lists from FILE2 replace FILE1. If MERGED_OUT is omitted, print to stdout.\n"
84+
)
85+
sys.exit(2)
86+
87+
base_path, override_path = sys.argv[1], sys.argv[2]
88+
out_path = sys.argv[3] if argc == 4 else None
89+
90+
with open(base_path, encoding="utf-8") as f:
91+
base = yaml.safe_load(f)
92+
with open(override_path, encoding="utf-8") as f:
93+
override = yaml.safe_load(f)
94+
95+
if base is None:
96+
base = {}
97+
if override is None:
98+
override = {}
99+
100+
if not isinstance(base, dict):
101+
sys.stderr.write(
102+
f"{base_path}: root must be a mapping, got {type(base).__name__}\n"
103+
)
104+
sys.exit(3)
105+
if not isinstance(override, dict):
106+
sys.stderr.write(
107+
f"{override_path}: root must be a mapping, got {type(override).__name__}\n"
108+
)
109+
sys.exit(3)
110+
111+
merged = deep_merge(base, override)
112+
dump_options = dict(
113+
default_flow_style=False,
114+
sort_keys=False,
115+
allow_unicode=True,
116+
)
117+
if out_path is None:
118+
yaml.dump(merged, sys.stdout, Dumper=DoubleQuoteDumper, **dump_options)
119+
else:
120+
with open(out_path, "w", encoding="utf-8") as out_f:
121+
yaml.dump(merged, out_f, Dumper=DoubleQuoteDumper, **dump_options)
122+
123+
124+
if __name__ == "__main__":
125+
main()

roles/reproducer/tasks/overwrite_zuul_vars.yml

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,50 @@
1515

1616
- name: Overwrite reproducer-variables.yml when ZIronic used
1717
when: _current_zuul_vars.stat.exists
18+
vars:
19+
temp_reproducer_var_path: /tmp/reproducer-variables.yml
20+
temp_merged_reproducer_var_path: /tmp/merged-reproducer-variables.yml
1821
block:
19-
- name: Dump reproducer-variables.yml to var
22+
- name: Slurp reproducer-variables.yml to hypervisor
2023
ansible.builtin.slurp:
2124
src: "{{ cifmw_basedir }}/parameters/reproducer-variables.yml"
2225
register: reproducer_original
2326
delegate_to: controller-0
2427
no_log: "{{ cifmw_nolog | default(true) | bool }}"
2528

26-
- name: Dump zuul_vars.yaml to var
27-
ansible.builtin.slurp:
28-
src: "{{ ansible_user_dir }}/configs/zuul_vars.yaml"
29-
register: zuul_job_vars
29+
- name: Create temp file reproducer-variables.yml on hypervisor
30+
ansible.builtin.copy:
31+
content: "{{ reproducer_original.content | b64decode }}"
32+
dest: "{{ temp_reproducer_var_path }}"
33+
mode: "0664"
3034
no_log: "{{ cifmw_nolog | default(true) | bool }}"
3135

32-
- name: Load current job zuul_vars.yaml
33-
ansible.builtin.include_vars:
34-
file: "{{ ansible_user_dir }}/configs/zuul_vars.yaml"
35-
name: _current_zuul_vars_include
36+
- name: Copy merge yamls script
37+
become: true
38+
ansible.builtin.copy:
39+
src: merge_yaml_override.py
40+
dest: /usr/local/bin/merge_yaml_override
41+
mode: "0755"
3642

37-
- name: Decode reproducer-variables.yml content
38-
ansible.builtin.set_fact:
39-
reproducer_vars_content: "{{ reproducer_original.content | b64decode | from_yaml }}"
43+
- name: Execute merge script
44+
ansible.builtin.shell: >
45+
python3 /usr/local/bin/merge_yaml_override
46+
{{ temp_reproducer_var_path }}
47+
{{ ansible_user_dir }}/configs/zuul_vars.yaml >
48+
{{ temp_merged_reproducer_var_path }}
4049
no_log: "{{ cifmw_nolog | default(true) | bool }}"
4150

42-
- name: Filter zuul vars to only keys present in reproducer-variables
43-
ansible.builtin.set_fact:
44-
_zuul_vars_filtered: >-
45-
{{
46-
_current_zuul_vars_include | dict2items
47-
| selectattr('key', 'in', reproducer_vars_content.keys())
48-
| items2dict
49-
}}
51+
- name: Slurp merged reproducer-variables.yml from hypervisor
52+
ansible.builtin.slurp:
53+
src: "{{ temp_merged_reproducer_var_path }}"
54+
register: merged_reproducer_slurp
5055
no_log: "{{ cifmw_nolog | default(true) | bool }}"
5156

5257
- name: Write back merged reproducer-variables.yml
5358
ansible.builtin.copy:
54-
content: >-
55-
{{ reproducer_vars_content | combine(_zuul_vars_filtered, recursive=true) | to_nice_yaml }}
59+
content: "{{ merged_reproducer_slurp.content | b64decode }}"
5660
dest: "{{ cifmw_basedir }}/parameters/reproducer-variables.yml"
57-
mode: '0664'
61+
mode: "0664"
5862
backup: true
5963
no_log: "{{ cifmw_nolog | default(true) | bool }}"
6064
delegate_to: controller-0

0 commit comments

Comments
 (0)