From 7e09dcebd7715b8ebfe6d9ee7449f82f0c0d578d Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 13:38:04 +0200 Subject: [PATCH 1/3] 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 6ea20d647e6202212ebda792e7d1a183c652098e Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 13:58:41 +0200 Subject: [PATCH 2/3] 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 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/utils/ansible_roles_to_collection.py b/utils/ansible_roles_to_collection.py index 45891b15b14..5297049d474 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -342,11 +342,24 @@ 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.""" + try: + from ssg.constants import min_ansible_version + except ImportError: + min_ansible_version = "2.9" + 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 +662,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 f57855f7d1011c04b69bffda8d537b31a7025ed9 Mon Sep 17 00:00:00 2001 From: Gabriel Becker Date: Wed, 1 Jul 2026 14:34:38 +0200 Subject: [PATCH 3/3] 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 5297049d474..83dd2add7be 100644 --- a/utils/ansible_roles_to_collection.py +++ b/utils/ansible_roles_to_collection.py @@ -434,14 +434,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 = [] @@ -469,6 +506,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 @@ -643,8 +685,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)