Sync from SUSE:SLFO:Main ansible-sap-operations revision 0375477658de126dcf3d7332c4a01ccd

This commit is contained in:
2025-04-28 17:02:07 +02:00
parent 747ee33a56
commit 385f4e47cb
5 changed files with 350 additions and 36 deletions

BIN
ansible-sap-operations-0.9.1.tar.gz (Stored with Git LFS)

Binary file not shown.

View File

@@ -1,5 +1,11 @@
-------------------------------------------------------------------
Thu Apr 10 14:38:36 UTC 2025 - Marcel Mamula <marcel.mamula@suse.com>
- Updated downstream transformation
- Removed python dependencies and macros from spec file
-------------------------------------------------------------------
Fri Jan 20 09:00:00 UTC 2025 - Marcel Mamula <marcel.mamula@suse.com>
- 0.9.1
- Initial release.
- Initial release.

View File

@@ -16,7 +16,6 @@
#
%define ansible_collection_name sap_operations
%define ansible_roles_to_remove "sap_rhsm sap_fapolicy"
%define ansible_collection_path %{_datadir}/ansible/collections/ansible_collections/suse/%{ansible_collection_name}
@@ -26,37 +25,20 @@ License: Apache-2.0
Version: 0.9.1
Release: 0
URL: https://github.com/SUSE/community.sap_operations/
Source: %{url}archive/refs/tags/%{version}-suse.tar.gz#/%{name}-%{version}.tar.gz
Source0: %{url}archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz
Source1: ansible-sap-operations.yaml
Source2: collection_update.py
BuildArch: noarch
# Python macros are required for python detection
BuildRequires: python-rpm-macros
Requires: ansible-core >= 2.16
Requires: ansible >= 9
BuildRequires: ansible-core >= 2.16
BuildRequires: ansible >= 9
# Minimum python version
%{?sle15_python_module_pythons}
BuildRequires: %{python_module base >= 3.11}
Requires: %{python_module base >= 3.11}
# Select correct supported Ansible
%if 0%{?suse_version} >= 1600
Requires: ansible-core
Requires: ansible
BuildRequires: ansible-core
BuildRequires: ansible
%else
# Only Ansible 9 is supported on SLES 15
Requires: ansible-core-2.16
Requires: ansible-9
BuildRequires: ansible-core-2.16
BuildRequires: ansible-9
%endif
# Do not check any files in collections for requires
%global __requires_exclude_from ^%{python311_sitelib}/.*$
# Python module ruamel.yaml for collection-update.py
BuildRequires: python3-ruamel.yaml
%description
@@ -73,14 +55,12 @@ update SAP profiles, configure firewall rules, execute RFC, etc.
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}
%build
cd %{_builddir}
# Remove unsupported roles before building collection
for role in $(echo %{ansible_roles_to_remove}); do
rm -rf %{_builddir}/roles/$role
done
# Build the Ansible collection
ansible-galaxy collection build --output-path %{_builddir}

View File

@@ -0,0 +1,65 @@
# Configuration file for collection changes before build.
# Collection: community.sap_operations
# 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.
changes:
# Main collection changes
- type: remove_paths
# Remove all unsupported roles.
# Remove all 'meta/main.yml' files within any role directory.
paths:
- "roles/sap_rhsm"
- "roles/sap_fapolicy"
- "roles/*/meta/main.yml"
- type: remove_lines
# Remove specific lines from README.md and all role README.md files.
# This is used to clean up any mention of unsupported roles and leftover Ansible Lint markers.
files:
- "README.md"
- "roles/*/README.md"
patterns:
- "sap_rhsm"
- "sap_fapolicy"
- "\\[Ansible Lint"
- 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'.
files:
- "galaxy.yml"
- "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_operations/blob/main/README.md" }
- type: update_yaml_key
# Update the 'authors' key in galaxy.yml to 'SUSE'.
files:
- "galaxy.yml"
key: "authors"
value:
- SUSE
- type: update_yaml_key
# 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.
files:
- "galaxy.yml"
key: "build_ignore"
value:
- "tests"
- ".git*"
- ".ansible-lint"
- ".yamllint*"
- ".pylintrc*"
- "bindep*"
- ".pre-commit-config.yaml"

263
collection_update.py Normal file
View File

@@ -0,0 +1,263 @@
# This Python script updates downstream collection details before execution of ansible-galaxy build.
# Intended use: Executed by RPM 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 within the build directory.
Args:
build_dir (str): The base directory where the files/directories are located.
paths (list): A list of path patterns (relative to build_dir) to remove.
These can be file paths or directory paths.
Supports glob patterns.
"""
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 specified files within the build directory.
Args:
build_dir (str): The base directory where the files are located.
files_patterns (list): A list of file path patterns (relative to build_dir) to modify.
Supports glob patterns.
patterns (list): A list of regular expression patterns.
Lines matching any of these patterns will be removed.
"""
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 the specified files within the build directory.
Args:
build_dir (str): The base directory where the files are located.
files_patterns (list): A list of file path patterns (relative to build_dir) to modify.
Supports glob patterns.
replacements (list): A list of dictionaries, each with 'find' and 'replace' keys.
'find' is the regular expression pattern to search for.
'replace' is the text to replace it with.
"""
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, key_path, value):
"""Updates a specific key's value in the specified YAML files, preserving the header.
Args:
build_dir (str): The base directory where the YAML files are located.
files (list): A list of file paths (relative to build_dir) to modify.
key_path (str): The path to the key to update, using dot notation (e.g., 'a.b.c').
value (any): The new value to set for the specified key.
"""
for file_path in files:
full_path = os.path.join(build_dir, file_path)
if os.path.exists(full_path):
try:
relative_path = os.path.relpath(full_path, build_dir)
header_lines = read_yaml_header(full_path)
with open(full_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(full_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, list_key_path, new_item):
"""Appends a new item to a list in the specified YAML files, preserving the header.
Args:
build_dir (str): The base directory where the YAML files are located.
files (list): A list of file paths (relative to build_dir) to modify.
list_key_path (str): The path to the list to append to, using dot notation (e.g., 'a.b.c').
new_item (any): The new item to append to the list.
"""
for file_path in files:
full_path = os.path.join(build_dir, file_path)
if os.path.exists(full_path):
try:
relative_path = os.path.relpath(full_path, build_dir)
header_lines = read_yaml_header(full_path)
with open(full_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(full_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'))
else:
print(f"Unknown changes type: {change['type']}")
if __name__ == "__main__":
main()