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

7.7 KiB

Assignment

The Nautilus DevOps team had a discussion about, how they can train different team members to use Ansible for different automation tasks. There are numerous ways to perform a particular task using Ansible, but we want to utilize each aspect that Ansible offers. The team wants to utilise Ansible's conditionals to perform the following task:

An inventory file is already placed under /home/thor/ansible directory on jump host, with all the Stratos DC app servers included.

Create a playbook /home/thor/ansible/playbook.yml and make sure to use Ansible's when conditionals statements to perform the below given tasks.

Copy blog.txt file present under /usr/src/data directory on jump host to App Server 1 under /opt/data directory. Its user and group owner must be user tony and its permissions must be 0644 .

Copy story.txt file present under /usr/src/data directory on jump host to App Server 2 under /opt/data directory. Its user and group owner must be user steve and its permissions must be 0644 .

Copy media.txt file present under /usr/src/data directory on jump host to App Server 3 under /opt/data directory. Its user and group owner must be user banner and its permissions must be 0644.

NOTE: You can use ansible_nodename variable from gathered facts with when condition. Additionally, please make sure you are running the play for all hosts i.e use - hosts: all.

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 Playbook — per-server copies using when conditionals

Create /home/thor/ansible/playbook.yml so ansible-playbook -i inventory playbook.yml copies a different file to each app server, selected with when conditionals on a single hosts: all play — with no extra arguments.

Note: this is an Ansible task, not Kubernetes — no manifests to pipe into kubectl. The heredoc below writes the playbook.

Step 0 — Confirm the inventory hostnames

The conditionals branch on the hostnames as written in your inventory, so read them first:

cat /home/thor/ansible/inventory

Use exactly the names it lists (stapp01, stapp02, stapp03 in the standard setup) in the when: lines below. Also confirm ansible_become_pass is present, since the play needs root and -K can't be passed.

Step 1 — Playbook

cat > /home/thor/ansible/playbook.yml <<'EOF'
---
- name: Copy files to app servers using conditionals
  hosts: all
  become: yes
  tasks:
    - name: Copy blog.txt to App Server 1
      ansible.builtin.copy:
        src: /usr/src/data/blog.txt
        dest: /opt/data/blog.txt
        owner: tony
        group: tony
        mode: '0644'
      when: inventory_hostname == "stapp01"

    - name: Copy story.txt to App Server 2
      ansible.builtin.copy:
        src: /usr/src/data/story.txt
        dest: /opt/data/story.txt
        owner: steve
        group: steve
        mode: '0644'
      when: inventory_hostname == "stapp02"

    - name: Copy media.txt to App Server 3
      ansible.builtin.copy:
        src: /usr/src/data/media.txt
        dest: /opt/data/media.txt
        owner: banner
        group: banner
        mode: '0644'
      when: inventory_hostname == "stapp03"
EOF

How it works

The conditional pattern

With hosts: all, every task is evaluated against every host. The when: clause is what narrows each one:

  • On stapp01, task 1's condition is true → it runs; tasks 2 and 3 are false → skipped.
  • On stapp02, only task 2 runs. On stapp03, only task 3.

So the play visits all three servers, and each executes exactly the one task meant for it. The PLAY RECAP will show roughly ok=2 changed=1 skipped=2 per host — skipped tasks are expected and correct here, not a failure.

This is the opposite structural choice from writing three separate plays (hosts: stapp01, etc.). Both produce the same end state; the task explicitly asks for the conditional form, which is what you'd reach for when the differences are small and you want one play covering the fleet.

Why inventory_hostname

inventory_hostname is a magic variable holding the host's name exactly as written in the inventory file. That makes it the most reliable thing to branch on:

  • You can read its value directly (cat inventory) — no guessing, no remote lookup.
  • It doesn't depend on fact gathering.
  • It can't drift from what the inventory says, because it is what the inventory says.

The alternative — ansible_nodename: the task's note mentions it, and it also works, but it's a gathered fact reporting whatever name the operating system is configured with. That's often a fully-qualified domain name rather than the short one, and the exact value depends on how the lab's hosts are configured. Comparing it against the wrong string matches nothing, every task silently skips, and the playbook reports success with no work done — a failure mode that looks like a pass until you inspect the files.

If you prefer that route, get the real values first rather than assuming them:

ansible -i inventory all -m setup -a "filter=ansible_nodename"

then paste the exact output into the conditions:

      when: ansible_nodename == "<exact value printed for stapp01>"

Either variable satisfies the requirement — the task says you can use ansible_nodename, not that you must, and validation checks the resulting files rather than which variable you branched on. inventory_hostname is simply the version with nothing left to verify.

The copy task

  • src: /usr/src/data/blog.txt — a path on the control node (the jump host). copy reads from the controller by default, which is exactly "copy from jump host to app server." (Copying between two paths on the remote machine would need remote_src: yes — not the case here.)
  • dest: /opt/data/blog.txt — the explicit target path, filename included. Clearer than relying on dest: /opt/data/ directory-expansion.
  • owner / group — the required per-server user. Hardcoded here because each task is already pinned to one host by its when:, so the literal value is unambiguous and matches the requirement text directly.
  • mode: '0644'quoted. Unquoted octal like 0644 is a classic YAML misparse that silently yields wrong permissions.

Why become: yes

/opt/data is root-owned, so writing there requires escalation — and setting owner/group runs chown, which is privileged regardless. The play escalates once at play level, with the sudo password supplied by ansible_become_pass from the inventory.

If /opt/data doesn't already exist on the targets, copy fails with "Destination directory does not exist" — it won't create missing parents. Add a file/state: directory task before the copies if you hit that.

Verify

cd /home/thor/ansible

# Connectivity
ansible -i inventory all -m ping

# The actual validation command
ansible-playbook -i inventory playbook.yml

# Each file, ownership and permissions
ansible -i inventory stapp01 -b -m command -a "ls -l /opt/data/blog.txt"
ansible -i inventory stapp02 -b -m command -a "ls -l /opt/data/story.txt"
ansible -i inventory stapp03 -b -m command -a "ls -l /opt/data/media.txt"

Expected — playbook failed=0 with skipped=2 on each host (normal for this pattern), and ls -l showing -rw-r--r-- with tony tony, steve steve, and banner banner respectively.

If every task shows skipped and no files appear, the when: strings don't match the actual hostnames — recheck them against cat inventory. That's the classic failure mode for conditionals: a run that reports success while doing nothing.