# 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 (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.