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

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.ymlthey 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 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:

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