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

189 lines
7.8 KiB
Markdown

# Assignment
One of the Nautilus DevOps team members is working on to develop a role for httpd installation and configuration. Work is almost completed, however there is a requirement to add a jinja2 template for index.html file. Additionally, the relevant task needs to be added inside the role. The inventory file ~/ansible/inventory is already present on jump host that can be used. Complete the task as per details mentioned below:
a. Update ~/ansible/playbook.yml playbook to run the httpd role on App Server 2.
b. Create a jinja2 template index.html.j2 under /home/thor/ansible/role/httpd/templates/ directory and add a line This file was created using Ansible on <respective server> (for example This file was created using Ansible on stapp01 in case of App Server 1). Also please make sure not to hard code the server name inside the template. Instead, use inventory_hostname variable to fetch the correct value.
c. Add a task inside /home/thor/ansible/role/httpd/tasks/main.yml to copy this template on App Server 2 under /var/www/html/index.html. Also make sure that /var/www/html/index.html file's permissions are 0755.
d. The user/group owner of /var/www/html/index.html file must be respective sudo user of the server (for example tony in case of stapp01).
Note: Validation will try to run the playbook using command ansible-playbook -i inventory playbook.yml so please make sure the playbook works this way without passing any extra arguments.
# Solution
# Ansible Role — jinja2 template for `index.html`
Add a jinja2 template to the existing `httpd` role and wire it up so
`ansible-playbook -i inventory playbook.yml` deploys it to App Server 2 — with **no extra
arguments**.
> Note: this is an Ansible task, not Kubernetes — no manifests to pipe into `kubectl`. The heredocs
> below write/append the files.
## Step 0 — Inspect what already exists
The role is partially built. Look before you touch anything:
```bash
cat /home/thor/ansible/inventory
cat /home/thor/ansible/playbook.yml
cat /home/thor/ansible/role/httpd/tasks/main.yml
ls -l /home/thor/ansible/role/httpd/
```
Note the existing tasks in `main.yml`**they must be preserved**. Also confirm the inventory has
`stapp02` and an `ansible_become_pass`, since the role needs root and `-K` can't be passed.
## Step 1 — Update the playbook (a)
```bash
cat > /home/thor/ansible/playbook.yml <<'EOF'
---
- name: Install and configure httpd on App Server 2
hosts: stapp02
become: yes
roles:
- role/httpd
EOF
```
## Step 2 — Create the jinja2 template (b)
```bash
mkdir -p /home/thor/ansible/role/httpd/templates
cat > /home/thor/ansible/role/httpd/templates/index.html.j2 <<'EOF'
This file was created using Ansible on {{ inventory_hostname }}
EOF
```
**The quoted `<<'EOF'` is essential here** — it stops the shell from touching `{{ inventory_hostname }}`
so the literal jinja2 expression reaches the file intact.
## Step 3 — Append the task to the role (c, d)
```bash
cat >> /home/thor/ansible/role/httpd/tasks/main.yml <<'EOF'
- name: Deploy index.html from jinja2 template
ansible.builtin.template:
src: index.html.j2
dest: /var/www/html/index.html
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0755'
EOF
```
Note **`>>`** (append), not `>` (overwrite) — this preserves the role's existing install/service
tasks. Verify afterwards:
```bash
cat /home/thor/ansible/role/httpd/tasks/main.yml
```
## How it works
### The playbook and the role path
- **`hosts: stapp02`** — requirement (a) scopes the run to App Server 2 only.
- **`roles: - role/httpd`** — the role lives at `/home/thor/ansible/role/httpd`. Ansible's default
search path is `./roles/`, but this directory is named `role` (singular), so the relative path
`role/httpd` is given explicitly. Ansible resolves it relative to the **playbook's directory**,
which is exactly where it sits. Writing just `httpd` would fail with "the role 'httpd' was not
found."
- **`become: yes`** — the role installs packages, manages a service, and writes under root-owned
`/var/www/html/`. All privileged.
### The jinja2 template — `inventory_hostname`
Requirement (b) forbids hardcoding the server name. **`{{ inventory_hostname }}`** is an Ansible
*magic variable* holding the name of the host **as written in the inventory** — so it evaluates to
`stapp02` when the play runs against App Server 2, `stapp01` on App Server 1, and so on.
That's why the file is a **template** (`.j2`) rather than a static file: `template` renders jinja2
expressions at deploy time, substituting the correct value per host. A `copy` task would transfer
the literal text `{{ inventory_hostname }}` unrendered — the exact mistake the requirement is
guarding against.
The rendered result on App Server 2:
```
This file was created using Ansible on stapp02
```
> Related variables worth distinguishing: `inventory_hostname` is the inventory's name for the host
> (what's needed here), while `ansible_hostname` is the machine's actual short hostname discovered by
> fact-gathering. They often match, but the requirement names `inventory_hostname` explicitly.
### The template task
- **`ansible.builtin.template`** — renders jinja2 then copies. The counterpart to `copy` for dynamic
content.
- **`src: index.html.j2`** — a **bare filename**, no path. Inside a role, the `template` module
automatically searches the role's `templates/` directory, so `templates/index.html.j2` is found
without qualification. That's a role convention worth knowing — it's why the directory name matters.
- **`dest: /var/www/html/index.html`** — the deployed path.
- **`mode: '0755'`** — quoted so YAML parses it as a string; unquoted octal is a classic silent
misparse.
### Requirement (d) — owner via `{{ ansible_user }}`
The owner must be "the respective sudo user of the server" — `tony` on stapp01, `steve` on stapp02,
`banner` on stapp03. Those are precisely the `ansible_user` values already defined per host in the
inventory, so:
```yaml
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
```
resolves per host automatically — `steve` here, since the play targets stapp02. No conditionals
needed, and it stays correct if the role is later run against other servers. (This is the same
pattern as the earlier per-host ownership task.)
`group` uses the same value because Linux creates a matching primary group per user by default
(`steve` → group `steve`).
### Why appending matters
`tasks/main.yml` already contains the role's install and service tasks — the work "almost completed"
by the team member. Overwriting it with `>` would delete those and the role would no longer install
httpd, so the template task would land in a directory that doesn't exist. `>>` adds the new task to
the end, which is also the correct **order**: httpd must be installed (creating `/var/www/html/`)
before the template is written into it.
## Verify
```bash
cd /home/thor/ansible
# Existing role tasks still present, new task appended at the end
cat role/httpd/tasks/main.yml
# Connectivity
ansible -i inventory stapp02 -m ping
# The actual validation command
ansible-playbook -i inventory playbook.yml
# Rendered content, ownership, permissions
ansible -i inventory stapp02 -b -m command -a "cat /var/www/html/index.html"
ansible -i inventory stapp02 -b -m command -a "ls -l /var/www/html/index.html"
```
Expected — playbook `failed=0`; `cat` printing
**`This file was created using Ansible on stapp02`** (rendered, *not* the literal `{{ ... }}`); and
`ls -l` showing `-rwxr-xr-x` with owner/group `steve steve`.
> If the file contains a literal `{{ inventory_hostname }}`, the task used `copy` instead of
> `template`, or the shell expanded the heredoc — recheck that Step 2 used the **quoted** `<<'EOF'`.
> "The role 'httpd' was not found" ⇒ the `role/httpd` relative path is wrong.