Files
kodekloud-engineer/100 - days of devops/devops-85.md

179 lines
6.5 KiB
Markdown

# Assignment
The Nautilus DevOps team is testing various Ansible modules on servers in Stratos DC. They're currently focusing on file creation on remote hosts using Ansible. Here are the details:
a. Create an inventory file ~/playbook/inventory on jump host and include all app servers.
b. Create a playbook ~/playbook/playbook.yml to create a blank file /opt/app.txt on all app servers.
c. Set the permissions of the /opt/app.txt file to 0755.
d. Ensure the user/group owner of the /opt/app.txt file is tony on app server 1, steve on app server 2 and banner on app server 3.
Note: Validation will execute the playbook using the command ansible-playbook -i inventory playbook.yml, so ensure the playbook functions correctly without any additional arguments.
# Solution
# Ansible Inventory + Playbook — create `/opt/app.txt` with per-host ownership
Set up the jump host so `ansible-playbook -i inventory playbook.yml` creates a blank
`/opt/app.txt` on all three Stratos DC app servers, mode `0755`, owned by a **different user on
each host** — with **no extra arguments**.
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
> below write the two files.
## Step 1 — Inventory (all three app servers)
```bash
cat > ~/playbook/inventory <<'EOF'
[app_servers]
stapp01 ansible_user=tony ansible_ssh_pass=Ir0nM@n ansible_become_pass=Ir0nM@n
stapp02 ansible_user=steve ansible_ssh_pass=Am3ric@ ansible_become_pass=Am3ric@
stapp03 ansible_user=banner ansible_ssh_pass=BigGr33n ansible_become_pass=BigGr33n
[app_servers:vars]
ansible_connection=ssh
EOF
```
## Step 2 — Playbook
```bash
cat > ~/playbook/playbook.yml <<'EOF'
---
- name: Create /opt/app.txt on all app servers
hosts: all
become: yes
tasks:
- name: Create blank file with correct mode and ownership
ansible.builtin.file:
path: /opt/app.txt
state: touch
mode: '0755'
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
EOF
```
## How it works
### The neat trick: `{{ ansible_user }}` for per-host ownership
Requirement (d) wants a **different owner per server**`tony` on stapp01, `steve` on stapp02,
`banner` on stapp03. Notice those are exactly the SSH users already defined per host in the
inventory. So instead of writing three separate tasks with `when:` conditionals, the playbook
references the variable:
```yaml
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
```
Ansible evaluates `ansible_user` **per host** during the play, so it resolves to `tony` on stapp01,
`steve` on stapp02, and `banner` on stapp03 automatically. One task, three correct outcomes — and it
stays correct if a host is added or a username changes, since the inventory is the single source of
truth.
> `group` uses the same value because these systems create a matching primary group for each user
> (user `tony` → group `tony`), which is the Linux default. If your lab's groups differ, set `group`
> explicitly per host instead.
The alternative — hardcoding with conditionals — would look like this and is strictly worse:
```yaml
# NOT recommended, shown for contrast
- name: Set owner on stapp01
ansible.builtin.file:
path: /opt/app.txt
owner: tony
when: inventory_hostname == "stapp01"
# ...repeated for each host
```
### The inventory
Each app server is keyed by its **wiki server name** (`stapp01`, `stapp02`, `stapp03`), which
resolves from the jump host — so the inventory name doubles as the connection target and **no
`ansible_host` is needed**. Confirm with `getent hosts stapp01` if you want certainty.
| Variable | Purpose |
|----------|---------|
| `ansible_user` | SSH username — **and** the file owner, via the templating above. |
| `ansible_ssh_pass` | SSH password (password auth, not keys). |
| `ansible_become_pass` | Sudo password, needed because the play uses `become: yes`. |
| `ansible_connection` | Transport plugin, set once for the group via `[app_servers:vars]`. |
> **Verify every credential against your lab's wiki.** The pairs shown follow the common Stratos DC
> pattern but should be confirmed rather than assumed.
Since validation runs the bare command (no `-u`, `-k`, `-K`), **all** connection and escalation
details must live in the inventory.
### Why `become: yes` is required
`/opt` is root-owned, so the SSH users can't create a file there directly — the play escalates with
`become: yes`. Escalation is also what makes **`owner:`/`group:` work at all**: changing a file's
ownership requires root (`chown` is privileged). Without `become`, you'd get permission errors on
both the create and the chown.
Because sudo may prompt for a password and you can't pass `-K`, `ansible_become_pass` is set per
host in the inventory. (If the lab has passwordless sudo, the variable is simply unused — harmless
either way.)
### The `file` module options
- **`state: touch`** — creates the file if absent, leaving it blank. Exactly "create a blank file."
- **`mode: '0755'`** — quoted so YAML reads it as a string; unquoted octal like `0755` is a classic
misparse that silently produces the wrong permissions.
- **`owner` / `group`** — applied by the module after creation, which is why root privileges are
needed.
Note `state: touch` bumps timestamps on every run, so re-runs report **changed** rather than **ok**.
That's fine here; add `modification_time: preserve` and `access_time: preserve` if you want strict
idempotency.
### Host key checking
First-time SSH connections can fail on host-key verification. The safest fix — needing no extra
command-line arguments — is an `ansible.cfg` beside the playbook:
```bash
cat > ~/playbook/ansible.cfg <<'EOF'
[defaults]
host_key_checking = False
EOF
```
`sshpass` must also be installed on the jump host for password auth.
## Verify
```bash
cd ~/playbook
# All three hosts listed
ansible-inventory -i inventory --list
# Connectivity + credentials across every server
ansible -i inventory all -m ping
# The actual validation command
ansible-playbook -i inventory playbook.yml
# Confirm mode and per-host ownership
ansible -i inventory all -b -m command -a "ls -l /opt/app.txt"
```
Expected — `ping` returning `SUCCESS` for all three; the playbook finishing with `failed=0`; and the
final listing showing `-rwxr-xr-x` with owner/group `tony tony` on stapp01, `steve steve` on
stapp02, and `banner banner` on stapp03.
> "Missing sudo password" ⇒ `ansible_become_pass` absent or wrong. A chown failure ⇒ `become: yes`
> didn't take. Wrong owner ⇒ check that `ansible_user` in the inventory matches the required owner
> for that host.