From 7e09dcebd7715b8ebfe6d9ee7449f82f0c0d578d Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 13:38:04 +0200 Subject: [PATCH 1/4] Fix collection author exceeding 64-character Galaxy limit Remove email from COLLECTION_AUTHORS so the author string stays within the 64-character limit enforced by galaxy-importer. --- utils/ansible_roles_to_collection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/ansible_roles_to_collection.py b/utils/ansible_roles_to_collection.py index 66a3045a00b..45891b15b14 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -71,7 +71,7 @@ def _get_ssg_version(): COLLECTION_NAMESPACE = "redhatofficial" COLLECTION_NAME = "rhel_hardening_roles" COLLECTION_AUTHORS = [ - "ComplianceAsCode development team " + "ComplianceAsCode development team" ] COLLECTION_DESCRIPTION = ( "Ansible roles for RHEL system hardening, generated from ComplianceAsCode content." From a37496655b61eb94afc081291d0dfff65ae2a35b Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 13:58:41 +0200 Subject: [PATCH 2/4] Generate meta/runtime.yml in Ansible collection The galaxy-importer requires requires_ansible in meta/runtime.yml. Add generation of this file using ssg.constants.min_ansible_version as the minimum version (falls back to 2.9 if ssg is not importable). --- utils/ansible_roles_to_collection.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/utils/ansible_roles_to_collection.py b/utils/ansible_roles_to_collection.py index 45891b15b14..b7f34b1da04 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -342,11 +342,21 @@ def create_collection_dirs(output_dir, namespace, collection_name): collection_dir = os.path.join( output_dir, "ansible_collections", namespace, collection_name ) - for subdir in ("roles", os.path.join("plugins", "modules")): + for subdir in ("roles", os.path.join("plugins", "modules"), "meta"): os.makedirs(os.path.join(collection_dir, subdir), exist_ok=True) return collection_dir +def generate_runtime_yml(collection_dir): + """Write meta/runtime.yml declaring the minimum required Ansible version.""" + from ssg.constants import min_ansible_version + runtime_data = {"requires_ansible": ">=%s" % min_ansible_version} + runtime_yml_path = os.path.join(collection_dir, "meta", "runtime.yml") + with open(runtime_yml_path, "w", encoding="utf-8") as f: + yaml.dump(runtime_data, f, default_flow_style=False, allow_unicode=True) + print("Generated meta/runtime.yml") + + def generate_galaxy_yml( collection_dir, namespace, collection_name, version, description=COLLECTION_DESCRIPTION, documentation=None, @@ -649,6 +659,7 @@ def main(): homepage=args.homepage, issues=args.issues, ) + generate_runtime_yml(collection_dir) generate_readme(collection_dir, args.namespace, args.collection, roles) artifact_path = None From 171ec2cb9e7f6b4aa87c64e93d4068d82e8d461a Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 14:34:38 +0200 Subject: [PATCH 3/4] Rewrite role READMEs inside the collection for correct usage The roles are generated with standalone Galaxy references (RedHatOfficial.rhel9_stig). When bundled into a collection those references are wrong. After copying each role, rewrite its README to: - Use the collection FQCN (redhatofficial.rhel_hardening_roles.rhel9_stig) - Fix the broken relative link to defaults/main.yml which does not resolve in the Galaxy collection UI --- utils/ansible_roles_to_collection.py | 53 +++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/utils/ansible_roles_to_collection.py b/utils/ansible_roles_to_collection.py index b7f34b1da04..da60024e166 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -431,14 +431,51 @@ def _remove_bundled_collection_deps(meta_path, bundled_collections): yaml.dump(meta, f, default_flow_style=False, allow_unicode=True) -def copy_roles(roles_dirs, collection_dir, bundled_collections): +def _rewrite_role_readme(readme_path, role_name, namespace, collection_name): + """ + Update a role README copied into a collection: + - Replace standalone Galaxy install + usage with the collection FQCN form. + - Fix the relative link to defaults/main.yml, which does not resolve in the + Galaxy collection UI. + """ + with open(readme_path, "r", encoding="utf-8") as f: + content = f.read() + + fqcn = f"{namespace}.{collection_name}.{role_name}" + + # Replace standalone install instruction and role reference + import re + content = re.sub( + r"Run `ansible-galaxy install \S+` to\s+download and install the role\. " + r"Then, you can use the following playbook snippet to run the Ansible role:", + f"Install the `{namespace}.{collection_name}` collection, then use the " + f"following playbook snippet:", + content, + ) + # Replace standalone role reference in the playbook example + content = re.sub( + r"\{ role: \S+\." + re.escape(role_name) + r" \}", + f"{{ role: {fqcn} }}", + content, + ) + # Fix broken relative link — Galaxy collection UI does not serve role subdirectories + content = content.replace( + "[list of variables](defaults/main.yml)", + "`defaults/main.yml`", + ) + + with open(readme_path, "w", encoding="utf-8") as f: + f.write(content) + + +def copy_roles(roles_dirs, collection_dir, bundled_collections, namespace, collection_name): """ Copy Ansible roles from one or more source directories into the collection's roles/ directory. When the same role name appears in multiple source dirs the first occurrence wins (dirs are processed in the order supplied). Also strips collection dependencies that are now vendored from each role's - meta/main.yml. + meta/main.yml, and rewrites role READMEs to use the collection FQCN. """ roles_dest = os.path.join(collection_dir, "roles") roles_copied = [] @@ -466,6 +503,11 @@ def copy_roles(roles_dirs, collection_dir, bundled_collections): if os.path.isfile(meta_path): _remove_bundled_collection_deps(meta_path, bundled_collections) + # Rewrite README to reflect collection FQCN and fix broken links + readme_path = os.path.join(role_dest, "README.md") + if os.path.isfile(readme_path): + _rewrite_role_readme(readme_path, role_name, namespace, collection_name) + print(f"Copied {len(roles_copied)} roles into the collection.") return roles_copied @@ -640,8 +682,11 @@ def main(): args.output_dir, args.namespace, args.collection ) - # Copy roles (also strips vendored deps from meta) - roles = copy_roles(args.roles_dirs, collection_dir, list(modules_to_bundle.keys())) + # Copy roles (also strips vendored deps from meta and rewrites READMEs) + roles = copy_roles( + args.roles_dirs, collection_dir, list(modules_to_bundle.keys()), + args.namespace, args.collection, + ) # Copy vendored modules into plugins/modules/ bundle_modules(extracted_modules, collection_dir) From e067521444794fb063d75ec9bb46d73359f92538 Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Fri, 10 Jul 2026 11:50:13 +0200 Subject: [PATCH 4/4] Move inline imports to top of file --- utils/ansible_roles_to_collection.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/utils/ansible_roles_to_collection.py b/utils/ansible_roles_to_collection.py index da60024e166..00b487712a0 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -31,6 +31,7 @@ import argparse import json import os +import re import shutil import subprocess import sys @@ -49,6 +50,7 @@ if _UTILS_DIR not in sys.path: sys.path.insert(0, _UTILS_DIR) from ansible_playbook_to_role import PRODUCT_ALLOWLIST, PROFILE_DENYLIST +from ssg.constants import min_ansible_version SSG_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) @@ -109,8 +111,6 @@ def detect_modules_to_bundle(roles_dirs, collections_to_vendor): Returns a dict: {"community.general": ["ini_file", ...], "ansible.posix": [...]} """ - import re - fqcn_re = re.compile( r"(?:" + "|".join(re.escape(c) for c in collections_to_vendor) + r")\.\w+" ) @@ -349,7 +349,6 @@ def create_collection_dirs(output_dir, namespace, collection_name): def generate_runtime_yml(collection_dir): """Write meta/runtime.yml declaring the minimum required Ansible version.""" - from ssg.constants import min_ansible_version runtime_data = {"requires_ansible": ">=%s" % min_ansible_version} runtime_yml_path = os.path.join(collection_dir, "meta", "runtime.yml") with open(runtime_yml_path, "w", encoding="utf-8") as f: @@ -444,7 +443,6 @@ def _rewrite_role_readme(readme_path, role_name, namespace, collection_name): fqcn = f"{namespace}.{collection_name}.{role_name}" # Replace standalone install instruction and role reference - import re content = re.sub( r"Run `ansible-galaxy install \S+` to\s+download and install the role\. " r"Then, you can use the following playbook snippet to run the Ansible role:",