Automating Standard System Administration Tasks
The everyday module set
Package management: dnf (or package) with name and state: present|latest|absent. Services: service/systemd with state and enabled - set both, since starting a service does not enable it at boot. Identity: user and group with state, groups, append, password (hashed), shell. Files: file for state: directory|touch|absent|link plus owner, group, mode; copy for literal content (content: or src:); template for Jinja2 rendering; lineinfile/blockinfile for surgical edits. Storage: parted, lvg, lvol, filesystem, mount. Security: seboolean, sefcontext, firewalld (in ansible.posix). Scheduling: cron, at. Downloads and archives: get_url, archive, unarchive.
Traps
mount with state: mounted writes /etc/fstab and mounts, and the mount point directory must already exist - use file first. firewalld needs permanent: true for persistence and immediate: true (or a reload handler) to take effect now. lineinfile without regexp: appends on every run and is not idempotent. template renders and typically adds a trailing newline; copy with content: still requires dest:. service enabled: true alone leaves the unit stopped.
Worked pattern
Config-change-then-restart is the canonical idempotent pattern: a template task that changes the file notifies a handler that restarts the service, and a preceding dnf task installs the package. On an unchanged run nothing is notified, so no restart occurs.
Rule of thumb
Always ask "what happens on the second run?" - if the answer is not "nothing changes", the task is wrong.