Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 15 additions & 20 deletions reproducer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,23 @@
name: firewalld
state: restarted

# NOTE(dpawlik): Keep support for legacy workflow where
# controller host is created and actions are done not on
# hypervisor directly, but on controller.
# NOTE(dpawlik): Synchronize current Zuul repositories stored in $HOME/src
# to controller-0 to make sure all projects are in state as they should.
- name: Synchronize src dir with controller-0
when:
- not cifmw_deploy_reproducer_env | default(true) | bool
vars:
sync_dir: "{{ ansible_user_dir }}/src"
ansible.posix.synchronize:
src: "{{ sync_dir }}/"
dest: "zuul@controller-0:{{ sync_dir }}"
archive: true
recursive: true

# NOTE(dpawlik): After calling reproducer role using ZIronic tool,
# when the bootstrap phase has been completed, it generates a file
# "reproducer-variables.yml" that contains all variables done in the
# CI job. The problem here is, that "testing" phase might have other
# variables than what was in the bootstrap phase. It means, that
# we need to overwrite the variables with current CI job vars.
- name: Overwrite reproducer-variables.yml when ZIronic bootstrap
- name: Set hypervisor name if not defined
when: hypervisor is not defined
ansible.builtin.set_fact:
hypervisor: "{{ ansible_fqdn }}"

- name: Prepare job after earlier bootstrap by PreMetal
when:
- not cifmw_deploy_reproducer_env | default(true) | bool
ansible.builtin.include_role:
name: reproducer
tasks_from: overwrite_zuul_vars.yml
tasks_from: premetal.yml

- name: Run deployment if instructed to
when:
- cifmw_deploy_architecture | default(false) | bool
Expand All @@ -141,7 +133,10 @@
poll: 20
delegate_to: controller-0
ansible.builtin.command:
cmd: "$HOME/deploy-architecture.sh {{ cifmw_deploy_architecture_args | default('') }}"
cmd: >
$HOME/deploy-architecture.sh
-e hypervisor="{{ hypervisor }}"
{{ cifmw_deploy_architecture_args | default('') }}

- name: Run post deployment if instructed to
when:
Expand Down
118 changes: 118 additions & 0 deletions roles/reproducer/files/merge_yaml_override.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
#
# Copyright Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Merge two YAML files whose root is a mapping: keys from OVERRIDE replace BASE
# recursively for nested mappings; non-dict values (scalars, lists) are
# replaced entirely by OVERRIDE. Requires PyYAML (python3-pyyaml / python3-yaml
# on RPM systems).

from __future__ import annotations

import sys

try:
import yaml
except ImportError:
sys.stderr.write(
"merge_yaml_override.py: PyYAML is required "
"(e.g. dnf install python3-pyyaml or pip install pyyaml)\n"
)
sys.exit(1)


class DoubleQuoteDumper(yaml.SafeDumper):
"""SafeDumper that uses double quotes instead of single quotes."""

pass


def _str_representer(dumper, data):
if "\n" in data:
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
if any(c in data for c in "{}[]%*&!@#"):
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"')
return dumper.represent_scalar("tag:yaml.org,2002:str", data)


DoubleQuoteDumper.add_representer(str, _str_representer)


def deep_merge(base: object, override: object) -> object:
if base is None:
return override
if override is None:
return base
if isinstance(base, dict) and isinstance(override, dict):
out = dict(base)
for key, val in override.items():
if key in out:
out[key] = deep_merge(out[key], val)
else:
out[key] = val
return out
return override


def main() -> None:
argc = len(sys.argv)
if argc not in (3, 4):
sys.stderr.write(
f"usage: {sys.argv[0]} FILE1_BASE.yml FILE2_OVERRIDE.yml [MERGED_OUT.yml]\n"
" Deep-merge two YAML mappings: FILE2 wins on duplicate keys (nested dicts merged).\n"
" Scalars and lists from FILE2 replace FILE1. If MERGED_OUT is omitted, print to stdout.\n"
)
sys.exit(2)

base_path, override_path = sys.argv[1], sys.argv[2]
out_path = sys.argv[3] if argc == 4 else None

with open(base_path, encoding="utf-8") as f:
base = yaml.safe_load(f)
with open(override_path, encoding="utf-8") as f:
override = yaml.safe_load(f)

if base is None:
base = {}
if override is None:
override = {}

if not isinstance(base, dict):
sys.stderr.write(
f"{base_path}: root must be a mapping, got {type(base).__name__}\n"
)
sys.exit(3)
if not isinstance(override, dict):
sys.stderr.write(
f"{override_path}: root must be a mapping, got {type(override).__name__}\n"
)
sys.exit(3)

merged = deep_merge(base, override)
dump_kw = dict(
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
if out_path is None:
yaml.dump(merged, sys.stdout, Dumper=DoubleQuoteDumper, **dump_kw)
else:
with open(out_path, "w", encoding="utf-8") as out_f:
yaml.dump(merged, out_f, Dumper=DoubleQuoteDumper, **dump_kw)


if __name__ == "__main__":
main()
39 changes: 39 additions & 0 deletions roles/reproducer/tasks/overwrite_pull_secret.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
# No log in that file are necessary and it should not use: cifmw_nolog var.
- name: Slurp full pull-secret from secrets dir
ansible.builtin.slurp:
src: "{{ ansible_user_dir }}/secrets/pull_secret.json"
register: _full_pull_secret
delegate_to: hypervisor
no_log: true

- name: Get current cluster pull-secret
kubernetes.core.k8s_info:
kubeconfig: "{{ cifmw_openshift_kubeconfig | default(ansible_user_dir ~ '/.kube/config') }}"
kind: Secret
name: pull-secret
namespace: openshift-config
register: _cluster_pull_secret
delegate_to: controller-0
no_log: true

- name: Merge and update cluster pull-secret
vars:
_full_auths: "{{ (_full_pull_secret.content | b64decode | from_json).auths }}"
_cluster_auths: "{{ (_cluster_pull_secret.resources[0].data['.dockerconfigjson'] | b64decode | from_json).auths }}"
_merged:
auths: "{{ _cluster_auths | combine(_full_auths, recursive=true) }}"
kubernetes.core.k8s:
kubeconfig: "{{ cifmw_openshift_kubeconfig | default(ansible_user_dir ~ '/.kube/config') }}"
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: pull-secret
namespace: openshift-config
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ _merged | to_json | b64encode }}"
delegate_to: controller-0
no_log: true
50 changes: 27 additions & 23 deletions roles/reproducer/tasks/overwrite_zuul_vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,50 @@

- name: Overwrite reproducer-variables.yml when ZIronic used
when: _current_zuul_vars.stat.exists
vars:
temp_reproducer_var_path: /tmp/reproducer-variables.yml
temp_merged_reproducer_var_path: /tmp/merged-reproducer-variables.yml
block:
- name: Dump reproducer-variables.yml to var
- name: Slurp reproducer-variables.yml to hypervisor
ansible.builtin.slurp:
src: "{{ cifmw_basedir }}/parameters/reproducer-variables.yml"
register: reproducer_original
delegate_to: controller-0
no_log: "{{ cifmw_nolog | default(true) | bool }}"

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

- name: Load current job zuul_vars.yaml
ansible.builtin.include_vars:
file: "{{ ansible_user_dir }}/configs/zuul_vars.yaml"
name: _current_zuul_vars_include
- name: Copy merge yamls script
become: true
ansible.builtin.copy:
src: merge_yaml_override.py
dest: /usr/local/bin/merge_yaml_override
mode: "0755"

- name: Decode reproducer-variables.yml content
ansible.builtin.set_fact:
reproducer_vars_content: "{{ reproducer_original.content | b64decode | from_yaml }}"
- name: Execute merge script
ansible.builtin.shell: >
python3 /usr/local/bin/merge_yaml_override
{{ temp_reproducer_var_path }}
{{ ansible_user_dir }}/configs/zuul_vars.yaml >
{{ temp_merged_reproducer_var_path }}
no_log: "{{ cifmw_nolog | default(true) | bool }}"

- name: Filter zuul vars to only keys present in reproducer-variables
ansible.builtin.set_fact:
_zuul_vars_filtered: >-
{{
_current_zuul_vars_include | dict2items
| selectattr('key', 'in', reproducer_vars_content.keys())
| items2dict
}}
- name: Slurp merged reproducer-variables.yml from hypervisor
ansible.builtin.slurp:
src: "{{ temp_merged_reproducer_var_path }}"
register: merged_reproducer_slurp
no_log: "{{ cifmw_nolog | default(true) | bool }}"

- name: Write back merged reproducer-variables.yml
ansible.builtin.copy:
content: >-
{{ reproducer_vars_content | combine(_zuul_vars_filtered, recursive=true) | to_nice_yaml }}
content: "{{ merged_reproducer_slurp.content | b64decode }}"
dest: "{{ cifmw_basedir }}/parameters/reproducer-variables.yml"
mode: '0664'
mode: "0664"
backup: true
no_log: "{{ cifmw_nolog | default(true) | bool }}"
delegate_to: controller-0
18 changes: 18 additions & 0 deletions roles/reproducer/tasks/premetal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
- name: Synchronize src dir
ansible.builtin.include_tasks:
file: sync_src_dir.yml

# NOTE(dpawlik): After calling reproducer role using PreMetal tool,
# when the bootstrap phase has been completed, it generates a file
# "reproducer-variables.yml" that contains all variables done in the
# CI job. The problem here is, that "testing" phase might have other
# variables than what was in the bootstrap phase. It means, that
# we need to overwrite the variables with current CI job vars.
- name: Overwrite reproducer-variables.yml when PreMetal bootstrap
ansible.builtin.include_tasks:
file: overwrite_zuul_vars.yml

- name: Overwrite pull-secret.json and add missing registries
ansible.builtin.include_tasks:
file: overwrite_pull_secret.yml
54 changes: 54 additions & 0 deletions roles/reproducer/tasks/sync_src_dir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# NOTE(dpawlik): Synchronize data when ZIronic provision+bootstrap the
# hypervisor AND Zuul job uses ZIronic flavor which is executing
# job directly on hypervisor. When job starts, src and secrets dir are "recreated"
# by zuul-executor with current job data. Sync the data with
# controller-0 which is executing nested Ansible.
- name: Synchronize hypervisor src dir with controller-0
vars:
sync_dir: "{{ ansible_user_dir }}/src"
ansible.posix.synchronize:
src: "{{ sync_dir }}/"
dest: "zuul@controller-0:{{ sync_dir }}"
archive: true
recursive: true
no_log: "{{ cifmw_nolog | default(true) | bool }}"

# NOTE(dpawlik): Synchronize data when ZIronic provision+bootstrap the
# hypervisor BUT legacy workflow is used (spawning controller VM
# which is executing all other playbooks). When job starts, all
# data like secrets, src dir etc. is stored on controller VM, but because
# later tasks are using nested ansible execution, then all playbooks needs
# to be up-to-date.
# That case would be mostly used by developers. Synchronize module
# would ignore if sync would happen to the same host. No need to synchronize
# with controller-0 again, due tasks above would do that.
- name: Synchronize controller VM configs dir with hypervisor
vars:
sync_dir: "{{ ansible_user_dir }}/configs"
ansible.posix.synchronize:
src: "{{ sync_dir }}/"
dest: "zuul@{{ hypervisor }}:{{ sync_dir }}"
archive: true
recursive: true
no_log: "{{ cifmw_nolog | default(true) | bool }}"

- name: Synchronize controller secrets dir with hypervisor
vars:
sync_dir: "{{ ansible_user_dir }}/secrets"
ansible.posix.synchronize:
src: "{{ sync_dir }}/"
dest: "zuul@{{ hypervisor }}:{{ sync_dir }}"
archive: true
recursive: true
no_log: "{{ cifmw_nolog | default(true) | bool }}"

- name: Synchronize src dir with hypervisor
vars:
sync_dir: "{{ ansible_user_dir }}/src"
ansible.posix.synchronize:
src: "{{ sync_dir }}/"
dest: "zuul@{{ hypervisor }}:{{ sync_dir }}"
archive: true
recursive: true
no_log: "{{ cifmw_nolog | default(true) | bool }}"
Loading