Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ansible-sap-install for openSUSE:Factory checked in at 2025-07-10 22:13:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-sap-install (Old) and /work/SRC/openSUSE:Factory/.ansible-sap-install.new.7373 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-sap-install" Thu Jul 10 22:13:41 2025 rev:2 rq:1291666 version:1.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-sap-install/ansible-sap-install.changes 2025-04-20 19:49:31.374767262 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-sap-install.new.7373/ansible-sap-install.changes 2025-07-10 22:13:43.319641821 +0200 @@ -1,0 +2,29 @@ +Tue Jul 8 13:37:03 UTC 2025 - Marcel Mamula <marcel.mam...@suse.com> + +- 1.6.0 + - Minor Changes: + - New Feature - sap_anydb_install_oracle - Add handling of OS specific vars (#1033) + - New Feature - sap_ha_pacemaker_cluster - SLES16 support, new vars for ha_cluster for corosync and zypper patterns (#1056) + - New Feature - sap_storage_setup - Allow /software NFS mount (#1029) + - sap_*_preconfigure - Add missing RHEL 10.x vars (#1059) + - sap_general_preconfigure - Modify the kernel command line for SELinux also for RHEL 10 (#1036) + - sap_general_preconfigure - Use the correct sap.conf file for RHEL 10 (#1022) + - sap_ha_pacemaker_cluster/SUSE - SAP HANA scaleup post steps updated (#1061) + - sap_hana_install - Add opt-out for setting sidadm to noexpire (#1016) + - sap_swpm - Add opt-out for setting sidadm to noexpire (#1018) + + - Bugfixes: + - Collection - fix documentation link in galaxy.yml (#1009) + - sap_*_preconfigure - Make SELinux booleans persistent (#1013) + - sap_*_preconfigure/SUSE - Add retry attempts to zypper pattern installation (#1032) + - sap_*_preconfigure/SUSE - Update SLES16 pattern names and add packages for hardened images (#1057) + - sap_ha_pacemaker_cluster/SUSE - Remove python3-rpm dependency in pre_steps_hana (#1028) + - sap_hana_install - update README.md with Local Secure Store (LSS) installation details and examples (#1008) + - sap_maintain_etc_hosts - Resolve situation with empty domain, but not undefined (#1044) + - sap_swpm - Ensure sap_swpm_product_catalog_id is a string (#1035) + - sap_swpm - Fix TEMP handling (#1019) + - sap_swpm - Remove selinux role dependency from README.md (#1012) + - sap_swpm - Require inifile.params only on the managed node when reusing an existing inifile.params. (#1024) + - sap_swpm - Sanitize hostname length (#1042) + +------------------------------------------------------------------- Old: ---- ansible-sap-install-1.5.3.tar.gz collection_update.py New: ---- ansible-sap-install-1.6.0.tar.gz ansible-sap-install.rpmlintrc transformation.py ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-sap-install.spec ++++++ --- /var/tmp/diff_new_pack.lcG9IR/_old 2025-07-10 22:13:44.371685577 +0200 +++ /var/tmp/diff_new_pack.lcG9IR/_new 2025-07-10 22:13:44.375685743 +0200 @@ -15,34 +15,34 @@ # Please submit bugfixes or comments via https://bugs.opensuse.org/ # + %define ansible_collection_name sap_install %define ansible_collection_path %{_datadir}/ansible/collections/ansible_collections/suse/%{ansible_collection_name} - Name: ansible-sap-install Summary: Ansible collection suse.sap_install for SAP Automation License: Apache-2.0 -Version: 1.5.3 +Version: 1.6.0 Release: 0 URL: https://github.com/SUSE/community.sap_install/ Source0: %{url}archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz -Source1: ansible-sap-install.yaml -Source2: collection_update.py +Source1: %{name}.yaml +Source2: transformation.py +Source99: %{name}.rpmlintrc BuildArch: noarch -Requires: ansible-core >= 2.16 Requires: ansible >= 9 -BuildRequires: ansible-core >= 2.16 +Requires: ansible-core >= 2.16 BuildRequires: ansible >= 9 +BuildRequires: ansible-core >= 2.16 Requires: ansible-linux-system-roles # Python module ruamel.yaml for collection-update.py BuildRequires: python3-ruamel.yaml - %description This package provides a Ansible collection suse.sap_install. @@ -52,23 +52,16 @@ This collection can be used to simplify and accelerate the deployment, management, and operation of SAP systems on Linux platforms. - %prep -# Extract tarball -cd %{_builddir} -tar -xzf %{_sourcedir}/%{name}-%{version}.tar.gz --strip-components=1 - -# Execute python script to update documentation and remove unsupported roles -python3 %{_sourcedir}/collection_update.py --config %{_sourcedir}/%{name}.yaml --build_dir %{_builddir} - +%autosetup -p1 -n community.%{ansible_collection_name}-%{version} %build +# Execute python script to update documentation and remove unsupported roles +python3 %{_sourcedir}/transformation.py --config %{_sourcedir}/%{name}.yaml --build_dir . # Build the Ansible collection ansible-galaxy collection build --output-path %{_builddir} - %install -rm -rf %{buildroot} mkdir -p %{buildroot}%{_datadir}/ansible/collections mkdir -p %{buildroot}%{_datadir}/ansible/roles/ @@ -76,9 +69,8 @@ ansible-galaxy collection install --force %{_builddir}/suse-%{ansible_collection_name}-%{version}.tar.gz \ --collections-path %{buildroot}%{_datadir}/ansible/collections - %post -# Loop through roles in collection and create symlinks under %{_datadir}/ansible/roles/ +# Loop through roles in collection and create symlinks under /usr/share/ansible/roles/ # Installed community collection will take precedence over role symlinks. for role in %{ansible_collection_path}/roles/*; do role_name=$(basename "$role") @@ -88,9 +80,8 @@ fi done - %postun -# Loop through roles in %{_datadir}/ansible/roles/ and remove those that link to collection +# Loop through roles in /usr/share/ansible/roles/ and remove those that link to collection if [ "$1" -eq 0 ]; then for role in %{_datadir}/ansible/roles/community.%{ansible_collection_name}.*; do if [ -L "$role" ]; then @@ -102,7 +93,6 @@ done fi - %files %{_datadir}/ansible/collections/ %{_datadir}/ansible/roles/ ++++++ ansible-sap-install-1.5.3.tar.gz -> ansible-sap-install-1.6.0.tar.gz ++++++ ++++ 2189 lines of diff (skipped) ++++++ ansible-sap-install.rpmlintrc ++++++ # rpmlint configuration for ansible-sap-install package. # Ignore duplicate file warnings, because hard links and %dupes cannot be used. # update_firewall.yml: Wrapper code for calling firewall steps, that is part of pre_tasks and post_tasks in sap_swpm role. # handlers/main.yml: Simple handler task file for 2 roles. # post_steps_nwas_abap_ascs_ers.yml, post_steps_nwas_java_scs_ers.yml: Identical files, that are called from different sources. addFilter("W: files-duplicate") # Ignore error, because script is not executed from this location. # Ansible role will distribute file and set permission, when used. # roles/sap_hana_install/files/tmp/tail-f-hdblcm-install-trc.sh addFilter("E: non-executable-script") ++++++ ansible-sap-install.yaml ++++++ --- /var/tmp/diff_new_pack.lcG9IR/_old 2025-07-10 22:13:44.851705541 +0200 +++ /var/tmp/diff_new_pack.lcG9IR/_new 2025-07-10 22:13:44.855705707 +0200 @@ -1,51 +1,42 @@ -# Configuration file for collection changes before build. -# Collection: community.sap_install +# Configuration file for changes before build. -# Types of changes used by collection_update.py -# remove_paths - Remove specific directories and files that are no longer needed. -# remove_lines - Remove specific lines containing certain patterns from files. -# replace_text - Replace specific text strings within files. -# update_yaml_key - Update specific keys in YAML files with new values. -# append_yaml_list - Append items to a list in a YAML file. +# Types of available options for transformation: +# - remove_paths - Removes the specified files or directories. +# - remove_lines - Removes lines matching the patterns from the files matching specified glob patterns. +# - replace_text - Replaces text in files matching specified glob patterns. +# - update_yaml_key - Updates a specific key's value in the specified YAML files matching specified glob patterns. +# - append_yaml_list - Appends a new item to a list in the specified YAML files matching specified glob patterns. +# - set_yaml_value - Sets or replaces a specific key's value in the specified YAML files matching specified glob patterns. +# - remove_yaml_list_items - Removes specific items from a list in the specified YAML files matching specified glob patterns. changes: - # Main collection changes - - type: remove_paths - # Remove all 'meta/main.yml' files within any role directory. paths: + # Remove meta files within any role directory. - "roles/*/meta/main.yml" + - "roles/*/meta/collection-requirements.yml" + # Removal of other files is part of galaxy.yml build_ignore list. + - "roles/sap_hostagent" # Role does not support SLES. - type: remove_lines - # Remove specific lines from README.md and all role README.md files. - # This is used to clean up any leftover Ansible Lint markers. files: - "README.md" - "roles/*/README.md" patterns: - - "\\[Ansible Lint" + - "\\[Ansible Lint" # Remove Ansible Lint lines from README.md and all role README.md files. + - "sap_hostagent" # Remove unsupported role - type: replace_text - # Replace specific text strings in galaxy.yml and README.md files. - # This is used to update the namespace and repository links from 'community' to 'suse'. + # Update namespace to suse and downstream hyperlinks. files: - "galaxy.yml" - "README.md" + - "roles/*/README.md" replacements: - { find: "namespace: community", replace: "namespace: suse" } - { find: "github.com/sap-linuxlab/community", replace: "github.com/SUSE/community" } - { find: "documentation:.*", replace: "documentation: https://github.com/SUSE/community.sap_install/blob/main/README.md" } - - - type: replace_text - # Replace hyperlinks in README.md and all role README.md files. - # This is used to update the repository links from 'sap-linuxlab' to 'SUSE'. - files: - - "README.md" - - "roles/*/README.md" - replacements: - - { find: "github.com/sap-linuxlab/community.sap_install", replace: "github.com/SUSE/community.sap_install" } - - { find: "github.com/sap-linuxlab/community.sap_infrastructure", replace: "github.com/SUSE/community.sap_infrastructure" } - - { find: "github.com/sap-linuxlab/ansible.playbooks_for_sap", replace: "github.com/SUSE/ansible.playbooks_for_sap" } + - { find: "community.sap_install Ansible Collection", replace: "suse.sap_install Ansible Collection" } - type: update_yaml_key # Update the 'authors' key in galaxy.yml to 'SUSE'. @@ -55,9 +46,61 @@ value: - SUSE + - type: remove_lines + # Remove redhat.rhel_system_roles from roles, but keep it in choices + # Role only uses linux_system_roles only for selinux, which is not included yet. + files: + - "roles/*/README.md" + - "roles/*/defaults/main.yml" + - "roles/*/meta/argument_specs.yml" + patterns: + - "use 'redhat.rhel_system_roles'" + + - type: replace_text + # Update linux_system_roles names + files: + - "roles/*/README.md" + - "roles/*/defaults/main.yml" + - "roles/*/meta/argument_specs.yml" + replacements: + # Replace redhat.rhel_system_roles in choices with suse.linux_system_roles + - { find: "redhat.rhel_system_roles", replace: "suse.linux_system_roles" } + - { find: " - for the RHEL System Roles for SAP, or for Red Hat Automation Hub.<br>", replace: "<br>" } + # Replace fedora.linux_system_roles with suse.linux_system_roles where applicable. + - { find: "- _Default:_ `'fedora.linux_system_roles'`", replace: "- _Default:_ `'suse.linux_system_roles'`" } + - { find: "- _Default:_ `fedora.linux_system_roles`", replace: "- _Default:_ `suse.linux_system_roles`" } + - { find: ": 'fedora.linux_system_roles'", replace: ": 'suse.linux_system_roles'" } + # Ensure that choice comments are retained before replacing them. + - { find: "- `fedora.linux_system_roles` - for community/upstream.<br>", replace: "-X `fedora.linux_system_roles` - for community/upstream.<br>" } + - { find: "- `fedora.linux_system_roles`", replace: "- `suse.linux_system_roles`" } + - { find: "-X `fedora.linux_system_roles`", replace: "- `fedora.linux_system_roles`" } + - { find: "Ansible Collection `fedora.linux_system_roles`", replace: "Ansible Collection `suse.linux_system_roles`" } + - { find: "default: fedora.linux_system_roles", replace: "default: suse.linux_system_roles" } + # Replace collection names + - { find: "name: community.sap_install.", replace: "name: suse.sap_install." } + - { find: "default: 'community.sap_install'", replace: "default: 'suse.sap_install'" } + - { find: "_sap_install_collection: 'community.sap_install'", replace: "_sap_install_collection: 'suse.sap_install'" } + + - type: replace_text + # Update shebang + files: + - "roles/sap_install_media_detect/files/tmp/sapfile" + replacements: + - { find: "/usr/bin/env bash", replace: "/usr/bin/bash" } + + - type: remove_yaml_list_items + # This is used to remove specific collections from ansible_requirements.yml. + files: + - "requirements.yml" + list_key: "collections" + items_to_remove: + - name: "fedora.linux_system_roles" + - type: update_yaml_key + # NOTE: Empty lines after update_yaml_key will add them to dictionary! # Update the 'build_ignore' key in galaxy.yml to ignore specific files and directories during the build process. - # This is used to exclude test files, git directories, linting configuration files, and workflows. + # Playbooks contain direct role calls, not collections. + # Remove scripts and tests that are not required. files: - "galaxy.yml" key: "build_ignore" @@ -65,57 +108,19 @@ - "tests" - "roles/*/tests" - ".git*" - - "roles/.git*" - - "roles/*/.git*" + - "*/.git*" + - "*/*/.git*" - ".ansible-lint" - - "roles/*/.ansible-lint" + - "*/*/.ansible-lint" - ".yamllint*" - - "roles/*/.yamllint*" + - "*/*/.yamllint*" - ".pylintrc*" - "bindep*" - ".pre-commit-config.yaml" - "workflows" - - - - type: remove_lines - # Remove Red Hat collection from the sap_general_preconfigure, role sap_hana_preconfigure, sap_hana_install - # Role only uses linux_system_roles only for selinux, which is not included yet. - files: - - "roles/sap_general_preconfigure/README.md" - - "roles/sap_general_preconfigure/defaults/main.yml" - - "roles/sap_general_preconfigure/meta/argument_specs.yml" - - "roles/sap_hana_preconfigure/README.md" - - "roles/sap_hana_preconfigure/defaults/main.yml" - - "roles/sap_hana_preconfigure/meta/argument_specs.yml" - - "roles/sap_hana_install/defaults/main.yml" - patterns: - - "redhat.rhel_system_roles" - - - type: replace_text - # Update the '_sap_install_collection' variable from 'community.sap_install' to 'suse.sap_install' in specific files. - files: - - "roles/sap_general_preconfigure/defaults/main.yml" - - "roles/sap_swpm/defaults/main.yml" - replacements: - - { find: "_sap_install_collection: 'community.sap_install'", replace: "_sap_install_collection: 'suse.sap_install'" } - - - - type: remove_lines - # Remove Red Hat collection from the sap_ha_pacemaker_cluster role. - files: - - "roles/sap_ha_pacemaker_cluster/README.md" - - "roles/sap_ha_pacemaker_cluster/defaults/main.yml" - - "roles/sap_ha_pacemaker_cluster/meta/argument_specs.yml" - patterns: - - "redhat.rhel_system_roles" - - "For community/upstream, use 'fedora.linux_system_roles'" - - - type: replace_text - # Replace 'fedora.linux_system_roles' with 'suse.linux_system_roles' in specific files in the sap_ha_pacemaker_cluster role. - files: - - "roles/sap_ha_pacemaker_cluster/README.md" - - "roles/sap_ha_pacemaker_cluster/defaults/main.yml" - - "roles/sap_ha_pacemaker_cluster/meta/argument_specs.yml" - replacements: - - { find: "fedora.linux_system_roles", replace: "suse.linux_system_roles" } + - "changelogs/.plugin-cache.yaml" + - "playbooks" + - "tools/swpm2_parameters_inifile_generate.py" + - "roles/*/tools/beautify-assert-output.sh" +(No newline at EOF) ++++++ transformation.py ++++++ # This Python script updates upstream with downstream customizations before packaging. # All customizations are defined in separate yaml file. # List of available customizations: # - remove_paths - Removes the specified files or directories. # - remove_lines - Removes lines matching the patterns from the files matching specified glob patterns. # - replace_text - Replaces text in files matching specified glob patterns. # - update_yaml_key - Updates a specific key's value in the specified YAML files matching specified glob patterns. # - append_yaml_list - Appends a new item to a list in the specified YAML files matching specified glob patterns. # - set_yaml_value - Sets or replaces a specific key's value in the specified YAML files matching specified glob patterns. # - remove_yaml_list_items - Removes specific items from a list in the specified YAML files matching specified glob patterns. # Intended use: Executed by spec file during %prep import os import shutil import re import glob import argparse # Requires entry in spec file: BuildRequires: python3-ruamel.yaml from ruamel.yaml import YAML yaml = YAML() yaml.preserve_quotes = True # Preserves quotes yaml.indent(mapping=2, sequence=4, offset=2) # Preserves indents in yaml yaml.width = 4096 # Disable ruamel wrapping long lines def load_config(config_file): """Loads the configuration from a YAML file. """ with open(config_file, 'r') as f: return yaml.load(f) def remove_paths(build_dir, paths): """Removes the specified files or directories. This function iterates through a list of path patterns, finds all matching files and directories using glob, and removes them. This is useful for cleaning up a build directory by removing unnecessary files or directories before packaging. Args: build_dir (str): The absolute path to the base directory from which paths will be removed. paths (list[str]): A list of path patterns to remove. These paths are relative to `build_dir` and can include glob patterns (e.g., '*.txt', 'temp/**'). Example: ```yaml changes: - type: remove_paths paths: - "roles/*/meta/main.yml" ``` """ for path_pattern in paths: full_pattern = os.path.join(build_dir, path_pattern) for item in glob.glob(full_pattern): relative_path = os.path.relpath(item, build_dir) if os.path.isdir(item): print(f"Removed: {relative_path}") shutil.rmtree(item, ignore_errors=True) elif os.path.isfile(item): print(f"Removed: {relative_path}") os.remove(item) def remove_lines(build_dir, files_patterns, patterns): """Removes lines matching the patterns from the files matching specified glob patterns. This function finds files matching the given glob patterns and then reads each file to remove any lines that match any of the specified regular expression patterns. This is useful for cleaning up documentation or configuration files by removing obsolete or unwanted content. Args: build_dir (str): The absolute path to the base directory where the files are located. files_patterns (list[str]): A list of file path patterns to process. These paths are relative to `build_dir` and support glob patterns (e.g., 'docs/*.md', 'roles/**/tasks/main.yml'). patterns (list[str]): A list of regular expression patterns. Any line in the target files that matches one of these patterns will be removed. Example: ```yaml changes: - type: remove_lines files: - "README.md" - "roles/*/README.md" patterns: - "\\[Ansible Lint" ``` """ for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.isfile(file_path): relative_path = os.path.relpath(file_path, build_dir) with open(file_path, 'r') as f: lines = f.readlines() updated_lines = [ line for line in lines if not any(re.search(pattern, line) for pattern in patterns) ] with open(file_path, 'w') as f: f.writelines(updated_lines) print(f"Removed lines from: {relative_path}") def replace_text(build_dir, files_patterns, replacements): """Replaces text in files matching specified glob patterns. This function finds all files matching the given patterns and performs a series of find-and-replace operations on their content. The 'find' patterns are treated as regular expressions, allowing for powerful and flexible text manipulation. This is useful for updating project-wide values like namespaces, repository URLs, or other boilerplate text. Args: build_dir (str): The absolute path to the base directory where the files are located. files_patterns (list[str]): A list of file path patterns to process. These paths are relative to `build_dir` and support glob patterns (e.g., 'README.md', 'roles/**/*.yml'). replacements (list[dict[str, str]]): A list of replacement rules. Each rule is a dictionary with two keys: - 'find': The regular expression pattern to search for. - 'replace': The string to substitute for each match. Example: ```yaml changes: - type: replace_text files: - "galaxy.yml" replacements: - { find: "namespace: community", replace: "namespace: suse" } ``` """ for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.isfile(file_path): relative_path = os.path.relpath(file_path, build_dir) with open(file_path, 'r') as f: content = f.read() for replacement in replacements: content = re.sub( replacement['find'], replacement['replace'], content, flags=re.MULTILINE, ) with open(file_path, 'w') as f: f.write(content) print( f"Replaced text {replacement['find']} with {replacement['replace']} in: {relative_path}" ) def read_yaml_header(file_path): """Reads the header of a YAML file (lines before the first '---'). Args: file_path (str): The path to the YAML file. Returns: list: A list of lines representing the header of the YAML file. """ header_lines = [] with open(file_path, 'r') as f: for line in f: if line.strip() == '---': header_lines.append(line) break header_lines.append(line) return header_lines def update_yaml_key(build_dir, files_patterns, key_path, value): """Updates a specific key's value in the specified YAML files, preserving the header. This function finds all YAML files matching the given patterns and updates the value of a specified key. The key can be nested, using dot notation for the path (e.g., 'parent.child.key'). If the key or any part of its path does not exist, it will be created. The function is designed to preserve YAML formatting, including comments and indentation, by using `ruamel.yaml` and by explicitly handling non-YAML headers (lines before the first '---'). Args: build_dir (str): The absolute path to the base directory where the YAML files are located. files_patterns (list[str]): A list of file path patterns to process, relative to `build_dir`. Supports glob patterns (e.g., 'galaxy.yml', 'roles/**/meta.yml'). key_path (str): The dot-separated path to the key to update (e.g., 'info.version', 'authors'). value (any): The new value to assign to the key. This can be any YAML-compatible type (string, list, dictionary, etc.). Example: ```yaml changes: - type: update_yaml_key files: - "galaxy.yml" key: "build_ignore" value: - "tests" ``` """ for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.exists(file_path): try: relative_path = os.path.relpath(file_path, build_dir) header_lines = read_yaml_header(file_path) with open(file_path, 'r') as f: # Skip header lines when loading for _ in header_lines: next(f, None) data = yaml.load(f) keys = key_path.split('.') current_level = data for i, key in enumerate(keys): if i == len(keys) - 1: if key not in current_level: print( f"Warning: Key '{key_path}' not found in {relative_path}" ) else: current_level[key] = value print(f"Updated key '{key_path}' in: {relative_path}") else: if key not in current_level: current_level[key] = {} print( f"Created missing key '{key}' in path '{key_path}' in: {relative_path}" ) current_level = current_level[key] with open(file_path, 'w') as f: f.writelines(header_lines) yaml.dump(data, f) except Exception as e: print(f"Error processing YAML in {relative_path}: {e}") def append_yaml_list(build_dir, files_patterns, list_key_path, new_item): """Appends a new item to a list in the specified YAML files, preserving the header. This function finds all YAML files matching the given patterns and appends a new item to a specified list. The list is identified by a dot-separated key path. If the list or any part of its path does not exist, it will be created. The function ensures the item is not added if it already exists in the list, preventing duplicates. Like other YAML modification functions in this script, it preserves comments and formatting by using `ruamel.yaml` and handling file headers. Args: build_dir (str): The absolute path to the base directory where the YAML files are located. files_patterns (list[str]): A list of file path patterns to process, relative to `build_dir`. Supports glob patterns. list_key_path (str): The dot-separated path to the list to which the item will be appended (e.g., 'galaxy_info.platforms'). new_item (any): The item to append to the list. This can be a simple string or a more complex dictionary. Example: ```yaml changes: - type: append_yaml_list files: - "roles/*/meta/main.yml" list_key: "galaxy_info.platforms" new_item: name: "SLES" versions: - "15" ``` """ for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.exists(file_path): try: relative_path = os.path.relpath(file_path, build_dir) header_lines = read_yaml_header(file_path) with open(file_path, 'r') as f: # Skip header lines when loading for _ in header_lines: next(f, None) data = yaml.load(f) keys = list_key_path.split('.') current_level = data for i, key in enumerate(keys): if i == len(keys) - 1: if key not in current_level: current_level[key] = [] print( f"Created missing list '{list_key_path}' in: {relative_path}" ) if isinstance(current_level[key], list): if new_item not in current_level[key]: current_level[key].append(new_item) print( f"Appended item to list '{list_key_path}' in: {relative_path}" ) else: print( f"Item '{new_item}' already exists in list '{list_key_path}' in: {relative_path}" ) else: if key not in current_level: current_level[key] = {} print( f"Created missing key '{key}' in path '{list_key_path}' in: {relative_path}" ) current_level = current_level[key] with open(file_path, 'w') as f: f.writelines(header_lines) yaml.dump(data, f) except Exception as e: print(f"Error processing YAML in {relative_path}: {e}") def set_yaml_value(build_dir, files_patterns, key_path, value): """Sets or replaces a specific key's value in the specified YAML files, preserving the header. This function finds all YAML files matching the given patterns and sets a specified key to a new value. The key can be nested, using dot notation for the path (e.g., 'parent.child.key'). Unlike `update_yaml_key`, this function will always set the value. If the key or any part of its path does not exist, it will be created. This is useful for overwriting existing values or ensuring a key is present with a specific value. Args: build_dir (str): The absolute path to the base directory where the YAML files are located. files_patterns (list[str]): A list of file path patterns to process, relative to `build_dir`. Supports glob patterns. key_path (str): The dot-separated path to the key to set or create (e.g., 'collections', 'info.version'). value (any): The new value to assign to the key. This can be any YAML-compatible type (string, list, dictionary, etc.). Example: ```yaml changes: - type: set_yaml_value files: - "special_actions/*/ansible_requirements.yml" key: "collections" value: [] ``` """ for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.exists(file_path): relative_path = os.path.relpath(file_path, build_dir) try: header_lines = read_yaml_header(file_path) with open(file_path, 'r') as f: # Skip header lines when loading for _ in header_lines: next(f, None) data = yaml.load(f) keys = key_path.split('.') current_level = data for i, key in enumerate(keys): if i == len(keys) - 1: current_level[key] = value print(f"Set key '{key_path}' in: {relative_path}") else: if key not in current_level: current_level[key] = {} print( f"Created missing key '{key}' in path '{key_path}' in: {relative_path}" ) current_level = current_level[key] with open(file_path, 'w') as f: f.writelines(header_lines) yaml.dump(data, f) except Exception as e: print(f"Error processing YAML in {relative_path}: {e}") def remove_yaml_list_items(build_dir, files_patterns, list_key_path, items_to_remove): """Removes specific items from a list in the specified YAML files. This function finds all YAML files matching the given glob patterns and removes specified items from a list within those files. The list is identified by a dot-separated key path. Args: build_dir (str): The absolute path to the base directory where the YAML files are located. files_patterns (list[str]): A list of file path patterns to process, relative to `build_dir`. Supports glob patterns. list_key_path (str): The dot-separated path to the list from which items will be removed (e.g., 'collections'). items_to_remove (list[any]): A list of specification items. Any item in the target list that matches one of these specifications will be removed. Example: ```yaml changes: - type: remove_yaml_list_items files: - "deploy_scenarios/*/ansible_requirements.yml" list_key: "collections" items_to_remove: - name: "community.sap_install" ``` """ def item_matches(list_item, spec_item): """Check if a list item matches a specification item.""" if isinstance(list_item, dict) and isinstance(spec_item, dict): # For dicts, check if list_item contains all key-value pairs from spec_item. return all(k in list_item and list_item[k] == v for k, v in spec_item.items()) # For other types (like strings), use simple equality. return list_item == spec_item for file_pattern in files_patterns: full_pattern = os.path.join(build_dir, file_pattern) for file_path in glob.glob(full_pattern): if os.path.isfile(file_path): relative_path = os.path.relpath(file_path, build_dir) try: header_lines = read_yaml_header(file_path) with open(file_path, 'r') as f: for _ in header_lines: next(f, None) data = yaml.load(f) # Skip if there is nothing to update. if data is None: continue keys = list_key_path.split('.') current_level = data modified = False path_exists = True for i, key in enumerate(keys): if not (isinstance(current_level, dict) and key in current_level): path_exists = False break if i < len(keys) - 1: current_level = current_level[key] if not path_exists: print(f"Warning: Path '{list_key_path}' not found in {relative_path}. Skipping.") continue list_key = keys[-1] if isinstance(current_level[list_key], list): target_list = current_level[list_key] initial_len = len(target_list) new_list = [item for item in target_list if not any(item_matches(item, rem_spec) for rem_spec in items_to_remove)] if len(new_list) < initial_len: current_level[list_key] = new_list print(f"Removed {initial_len - len(new_list)} item(s) from list '{list_key_path}' in: {relative_path}") modified = True if modified: with open(file_path, 'w') as f: f.writelines(header_lines) yaml.dump(data, f) except Exception as e: print(f"Error processing YAML in {relative_path}: {e}") def main(): """Main function to load config and apply changes.""" parser = argparse.ArgumentParser(description="Change files in a specified directory based on a configuration file.") parser.add_argument("--build_dir", help="The path to the build directory where changes will occur." ) parser.add_argument("--config", help="The path to the configuration YAML file (default: config.yaml)." ) args = parser.parse_args() config_file = args.config build_dir = args.build_dir if not os.path.isdir(build_dir): print(f"Error: Build directory '{build_dir}' does not exist.") return config = load_config(config_file) changes = config.get('changes', []) for change in changes: change_type = change.get('type') if change_type == 'remove_paths': remove_paths(build_dir, change.get('paths', [])) elif change_type == 'remove_lines': remove_lines(build_dir, change.get('files', []), change.get('patterns', [])) elif change_type == 'replace_text': replace_text(build_dir, change.get('files', []), change.get('replacements', [])) elif change_type == 'update_yaml_key': update_yaml_key(build_dir, change.get('files', []), change.get('key'), change.get('value')) elif change_type == 'append_yaml_list': append_yaml_list(build_dir, change.get('files', []), change.get('list_key'), change.get('new_item')) elif change_type == 'set_yaml_value': set_yaml_value(build_dir, change.get('files', []), change.get('key'), change.get('value')) elif change_type == 'remove_yaml_list_items': remove_yaml_list_items(build_dir, change.get('files', []), change.get('list_key'), change.get('items_to_remove')) else: print(f"Unknown changes type: {change['type']}") if __name__ == "__main__": main()