7.8 KiB
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:
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)
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)
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)
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:
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 namedrole(singular), so the relative pathrole/httpdis given explicitly. Ansible resolves it relative to the playbook's directory, which is exactly where it sits. Writing justhttpdwould 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_hostnameis the inventory's name for the host (what's needed here), whileansible_hostnameis the machine's actual short hostname discovered by fact-gathering. They often match, but the requirement namesinventory_hostnameexplicitly.
The template task
ansible.builtin.template— renders jinja2 then copies. The counterpart tocopyfor dynamic content.src: index.html.j2— a bare filename, no path. Inside a role, thetemplatemodule automatically searches the role'stemplates/directory, sotemplates/index.html.j2is 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:
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
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 usedcopyinstead oftemplate, or the shell expanded the heredoc — recheck that Step 2 used the quoted<<'EOF'. "The role 'httpd' was not found" ⇒ therole/httpdrelative path is wrong.