Add ability to enroll RH-style systems (DNF5/DNF/RPM)
This commit is contained in:
parent
ad2abed612
commit
984b0fa81b
15 changed files with 1400 additions and 254 deletions
|
|
@ -166,6 +166,7 @@ def _write_playbook_all(path: str, roles: List[str]) -> None:
|
|||
pb_lines = [
|
||||
"---",
|
||||
"- name: Apply all roles on all hosts",
|
||||
" gather_facts: true",
|
||||
" hosts: all",
|
||||
" become: true",
|
||||
" roles:",
|
||||
|
|
@ -181,6 +182,7 @@ def _write_playbook_host(path: str, fqdn: str, roles: List[str]) -> None:
|
|||
"---",
|
||||
f"- name: Apply all roles on {fqdn}",
|
||||
f" hosts: {fqdn}",
|
||||
" gather_facts: true",
|
||||
" become: true",
|
||||
" roles:",
|
||||
]
|
||||
|
|
@ -468,6 +470,51 @@ def _render_generic_files_tasks(
|
|||
"""
|
||||
|
||||
|
||||
def _render_install_packages_tasks(role: str, var_prefix: str) -> str:
|
||||
"""Render cross-distro package installation tasks.
|
||||
|
||||
We generate conditional tasks for apt/dnf/yum, falling back to the
|
||||
generic `package` module. This keeps generated roles usable on both
|
||||
Debian-like and RPM-like systems.
|
||||
"""
|
||||
return f"""# Generated by enroll
|
||||
|
||||
- name: Install packages for {role} (APT)
|
||||
ansible.builtin.apt:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
update_cache: true
|
||||
when:
|
||||
- ({var_prefix}_packages | default([])) | length > 0
|
||||
- ansible_facts.pkg_mgr | default('') == 'apt'
|
||||
|
||||
- name: Install packages for {role} (DNF5)
|
||||
ansible.builtin.dnf5:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
when:
|
||||
- ({var_prefix}_packages | default([])) | length > 0
|
||||
- ansible_facts.pkg_mgr | default('') == 'dnf5'
|
||||
|
||||
- name: Install packages for {role} (DNF/YUM)
|
||||
ansible.builtin.dnf:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
when:
|
||||
- ({var_prefix}_packages | default([])) | length > 0
|
||||
- ansible_facts.pkg_mgr | default('') in ['dnf', 'yum']
|
||||
|
||||
- name: Install packages for {role} (generic fallback)
|
||||
ansible.builtin.package:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
when:
|
||||
- ({var_prefix}_packages | default([])) | length > 0
|
||||
- ansible_facts.pkg_mgr | default('') not in ['apt', 'dnf', 'dnf5', 'yum']
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def _prepare_bundle_dir(
|
||||
bundle: str,
|
||||
*,
|
||||
|
|
@ -629,6 +676,7 @@ def _manifest_from_bundle_dir(
|
|||
package_roles: List[Dict[str, Any]] = state.get("package_roles", [])
|
||||
users_snapshot: Dict[str, Any] = state.get("users", {})
|
||||
apt_config_snapshot: Dict[str, Any] = state.get("apt_config", {})
|
||||
dnf_config_snapshot: Dict[str, Any] = state.get("dnf_config", {})
|
||||
etc_custom_snapshot: Dict[str, Any] = state.get("etc_custom", {})
|
||||
usr_local_custom_snapshot: Dict[str, Any] = state.get("usr_local_custom", {})
|
||||
extra_paths_snapshot: Dict[str, Any] = state.get("extra_paths", {})
|
||||
|
|
@ -664,6 +712,7 @@ def _manifest_from_bundle_dir(
|
|||
|
||||
manifested_users_roles: List[str] = []
|
||||
manifested_apt_config_roles: List[str] = []
|
||||
manifested_dnf_config_roles: List[str] = []
|
||||
manifested_etc_custom_roles: List[str] = []
|
||||
manifested_usr_local_custom_roles: List[str] = []
|
||||
manifested_extra_paths_roles: List[str] = []
|
||||
|
|
@ -1041,6 +1090,157 @@ APT configuration harvested from the system (sources, pinning, and keyrings).
|
|||
|
||||
manifested_apt_config_roles.append(role)
|
||||
|
||||
# -------------------------
|
||||
# dnf_config role (DNF/YUM repos, config, and RPM GPG keys)
|
||||
# -------------------------
|
||||
if dnf_config_snapshot and dnf_config_snapshot.get("managed_files"):
|
||||
role = dnf_config_snapshot.get("role_name", "dnf_config")
|
||||
role_dir = os.path.join(roles_root, role)
|
||||
_write_role_scaffold(role_dir)
|
||||
|
||||
var_prefix = role
|
||||
|
||||
managed_files = dnf_config_snapshot.get("managed_files", [])
|
||||
excluded = dnf_config_snapshot.get("excluded", [])
|
||||
notes = dnf_config_snapshot.get("notes", [])
|
||||
|
||||
templated, jt_vars = _jinjify_managed_files(
|
||||
bundle_dir,
|
||||
role,
|
||||
role_dir,
|
||||
managed_files,
|
||||
jt_exe=jt_exe,
|
||||
jt_enabled=jt_enabled,
|
||||
overwrite_templates=not site_mode,
|
||||
)
|
||||
|
||||
if site_mode:
|
||||
_copy_artifacts(
|
||||
bundle_dir,
|
||||
role,
|
||||
_host_role_files_dir(out_dir, fqdn or "", role),
|
||||
exclude_rels=templated,
|
||||
)
|
||||
else:
|
||||
_copy_artifacts(
|
||||
bundle_dir,
|
||||
role,
|
||||
os.path.join(role_dir, "files"),
|
||||
exclude_rels=templated,
|
||||
)
|
||||
|
||||
files_var = _build_managed_files_var(
|
||||
managed_files,
|
||||
templated,
|
||||
notify_other=None,
|
||||
notify_systemd=None,
|
||||
)
|
||||
|
||||
jt_map = _yaml_load_mapping(jt_vars) if jt_vars.strip() else {}
|
||||
vars_map: Dict[str, Any] = {f"{var_prefix}_managed_files": files_var}
|
||||
vars_map = _merge_mappings_overwrite(vars_map, jt_map)
|
||||
|
||||
if site_mode:
|
||||
_write_role_defaults(role_dir, {f"{var_prefix}_managed_files": []})
|
||||
_write_hostvars(out_dir, fqdn or "", role, vars_map)
|
||||
else:
|
||||
_write_role_defaults(role_dir, vars_map)
|
||||
|
||||
tasks = "---\n" + _render_generic_files_tasks(
|
||||
var_prefix, include_restart_notify=False
|
||||
)
|
||||
with open(
|
||||
os.path.join(role_dir, "tasks", "main.yml"), "w", encoding="utf-8"
|
||||
) as f:
|
||||
f.write(tasks.rstrip() + "\n")
|
||||
|
||||
with open(
|
||||
os.path.join(role_dir, "meta", "main.yml"), "w", encoding="utf-8"
|
||||
) as f:
|
||||
f.write("---\ndependencies: []\n")
|
||||
|
||||
# README: summarise repos and GPG key material
|
||||
repo_paths: List[str] = []
|
||||
key_paths: List[str] = []
|
||||
repo_hosts: Set[str] = set()
|
||||
|
||||
url_re = re.compile(r"(?:https?|ftp)://([^/\s]+)", re.IGNORECASE)
|
||||
file_url_re = re.compile(r"file://(/[^\s]+)")
|
||||
|
||||
for mf in managed_files:
|
||||
p = str(mf.get("path") or "")
|
||||
src_rel = str(mf.get("src_rel") or "")
|
||||
if not p or not src_rel:
|
||||
continue
|
||||
|
||||
if p.startswith("/etc/yum.repos.d/") and p.endswith(".repo"):
|
||||
repo_paths.append(p)
|
||||
art_path = os.path.join(bundle_dir, "artifacts", role, src_rel)
|
||||
try:
|
||||
with open(art_path, "r", encoding="utf-8", errors="replace") as rf:
|
||||
for line in rf:
|
||||
s = line.strip()
|
||||
if not s or s.startswith("#") or s.startswith(";"):
|
||||
continue
|
||||
# Collect hostnames from URLs (baseurl, mirrorlist, metalink, gpgkey...)
|
||||
for m in url_re.finditer(s):
|
||||
repo_hosts.add(m.group(1))
|
||||
# Collect local gpgkey file paths referenced as file:///...
|
||||
for m in file_url_re.finditer(s):
|
||||
key_paths.append(m.group(1))
|
||||
except OSError:
|
||||
pass # nosec
|
||||
|
||||
if p.startswith("/etc/pki/rpm-gpg/"):
|
||||
key_paths.append(p)
|
||||
|
||||
repo_paths = sorted(set(repo_paths))
|
||||
key_paths = sorted(set(key_paths))
|
||||
repos = sorted(repo_hosts)
|
||||
|
||||
readme = (
|
||||
"""# dnf_config
|
||||
|
||||
DNF/YUM configuration harvested from the system (repos, config files, and RPM GPG keys).
|
||||
|
||||
## Repository hosts
|
||||
"""
|
||||
+ ("\n".join([f"- {h}" for h in repos]) or "- (none)")
|
||||
+ """\n
|
||||
## Repo files
|
||||
"""
|
||||
+ ("\n".join([f"- {p}" for p in repo_paths]) or "- (none)")
|
||||
+ """\n
|
||||
## GPG keys
|
||||
"""
|
||||
+ ("\n".join([f"- {p}" for p in key_paths]) or "- (none)")
|
||||
+ """\n
|
||||
## Managed files
|
||||
"""
|
||||
+ (
|
||||
"\n".join(
|
||||
[f"- {mf.get('path')} ({mf.get('reason')})" for mf in managed_files]
|
||||
)
|
||||
or "- (none)"
|
||||
)
|
||||
+ """\n
|
||||
## Excluded
|
||||
"""
|
||||
+ (
|
||||
"\n".join([f"- {e.get('path')} ({e.get('reason')})" for e in excluded])
|
||||
or "- (none)"
|
||||
)
|
||||
+ """\n
|
||||
## Notes
|
||||
"""
|
||||
+ ("\n".join([f"- {n}" for n in notes]) or "- (none)")
|
||||
+ """\n"""
|
||||
)
|
||||
with open(os.path.join(role_dir, "README.md"), "w", encoding="utf-8") as f:
|
||||
f.write(readme)
|
||||
|
||||
manifested_dnf_config_roles.append(role)
|
||||
|
||||
# -------------------------
|
||||
# etc_custom role (unowned /etc not already attributed)
|
||||
# -------------------------
|
||||
|
|
@ -1457,19 +1657,7 @@ User-requested extra file harvesting.
|
|||
f.write(handlers)
|
||||
|
||||
task_parts: List[str] = []
|
||||
task_parts.append(
|
||||
f"""---
|
||||
# Generated by enroll
|
||||
|
||||
- name: Install packages for {role}
|
||||
ansible.builtin.apt:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ({var_prefix}_packages | default([])) | length > 0
|
||||
|
||||
"""
|
||||
)
|
||||
task_parts.append("---\n" + _render_install_packages_tasks(role, var_prefix))
|
||||
|
||||
task_parts.append(
|
||||
_render_generic_files_tasks(var_prefix, include_restart_notify=True)
|
||||
|
|
@ -1616,19 +1804,7 @@ Generated from `{unit}`.
|
|||
f.write(handlers)
|
||||
|
||||
task_parts: List[str] = []
|
||||
task_parts.append(
|
||||
f"""---
|
||||
# Generated by enroll
|
||||
|
||||
- name: Install packages for {role}
|
||||
ansible.builtin.apt:
|
||||
name: "{{{{ {var_prefix}_packages | default([]) }}}}"
|
||||
state: present
|
||||
update_cache: true
|
||||
when: ({var_prefix}_packages | default([])) | length > 0
|
||||
|
||||
"""
|
||||
)
|
||||
task_parts.append("---\n" + _render_install_packages_tasks(role, var_prefix))
|
||||
task_parts.append(
|
||||
_render_generic_files_tasks(var_prefix, include_restart_notify=False)
|
||||
)
|
||||
|
|
@ -1667,6 +1843,7 @@ Generated for package `{pkg}`.
|
|||
manifested_pkg_roles.append(role)
|
||||
all_roles = (
|
||||
manifested_apt_config_roles
|
||||
+ manifested_dnf_config_roles
|
||||
+ manifested_pkg_roles
|
||||
+ manifested_service_roles
|
||||
+ manifested_etc_custom_roles
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue