Building a Reusable Role
Goal
You fold playbook pieces into a role to make a reusable unit, and learn to call the same role several times with different values.
Why it matters
The essence of a role is not "splitting code" but "fixing a convention so that it can be reused without configuration." A file placed in templates/ is found without writing a path, and a handler in handlers/main.yml can be used without any registration step. Thanks to this convention, you can guess the structure even of a role someone else wrote. What actually determines reusability is the choice between defaults and vars — if you put a value users should change in vars, its precedence is so high that it cannot be overridden from outside, and the role ends up being copied and edited and diverging again. And the habit of prefixing variable names with the role name pays off the moment you have more than two roles.
Steps
- Under
/root/ans/roles/roles/webapp, create thetasks,defaults,handlers,templates, andmetadirectories andtasks/main.yml. - In
/root/ans/roles/roles/webapp/defaults/main.yml, definewebapp_port: 8080andwebapp_root: /root/ans/roles/artifacts. - In
/root/ans/roles/roles/webapp/tasks/main.yml, add at least 2 named tasks, and make running them create the/root/ans/roles/artifactsdirectory. - Create
/root/ans/roles/roles/webapp/templates/webapp.conf.j2and render it to/root/ans/roles/artifacts/webapp.conf. The result must contain the lineport = 8080, and the template must reference thewebapp_portvariable. - In
/root/ans/roles/roles/webapp/handlers/main.yml, define arestart webapphandler andnotifyit from a task in/root/ans/roles/roles/webapp/tasks/main.yml. The handler creates/root/ans/roles/artifacts/restart.marker. - Create a
/root/ans/roles/roles/baselinerole (entry point/root/ans/roles/roles/baseline/tasks/main.yml) that leaves/root/ans/roles/artifacts/baseline.stamp, and declare it independenciesof/root/ans/roles/roles/webapp/meta/main.yml. - In
/root/ans/roles/site.yml, call thewebapprole twice. Call it once with the defaults (webapp.conf, port 8080), and once withwebapp_port: 9443so that it creates/root/ans/roles/artifacts/staging.conf. Do not copy the role directory. - Save the output of the second playbook run to
/root/ans/roles/out/run2.txtand theansible-lintresult to/root/ans/roles/out/lint.txt. The second run must showchanged=0and the handler must not run.
Notes
- Lab Pods start fresh for every lab. If
/root/ans/inventory/hosts.iniis missing, first recreate the same inventory you made in the first lab (web1, web2, db1,ansible_host=127.0.0.1,ansible_port=2222,ansible_user=root, with web and db in[prod:children]). For the structure, refer to/opt/lab/fixtures/ansible/inventory.sample.ini. ansible-galaxy role init roles/webappcreates the standard structure in one go.- When you call a role, variables written under
- role: webappapply only to that call. - Common mistake 1: writing a path that includes
templates/in the template. Inside a role, write only the file name. - Common mistake 2: copying the whole role for step 7. The point is to call the same source twice.
Create the role skeleton directories
Under /root/ans/roles/roles/webapp, create the tasks, defaults, handlers, templates, and meta directories and tasks/main.yml.
ansible-galaxy role init creates the standard structure for you. The directories you need are tasks/defaults/handlers/templates/meta.
Define two default values
In /root/ans/roles/roles/webapp/defaults/main.yml, define webapp_port: 8080 and webapp_root: /root/ans/roles/artifacts.
Put values that are expected to be overridden in defaults. Prefix the names with the role name.
Write and run the role tasks
In /root/ans/roles/roles/webapp/tasks/main.yml, add at least 2 named tasks, and make running them create the /root/ans/roles/artifacts directory.
tasks/main.yml is the entry point. You need at least 2 tasks, and all of them must have names.
Create a configuration file from a role template
Create /root/ans/roles/roles/webapp/templates/webapp.conf.j2 and render it to /root/ans/roles/artifacts/webapp.conf. The result must contain the line port = 8080, and the template must reference the webapp_port variable.
Files inside templates/ are found by name alone. Do not hard-code the port value; use the variable.
Define a role handler and trigger it
In /root/ans/roles/roles/webapp/handlers/main.yml, define a restart webapp handler and notify it from a task in /root/ans/roles/roles/webapp/tasks/main.yml. The handler creates /root/ans/roles/artifacts/restart.marker.
Handlers in handlers/main.yml are registered automatically. Leave a marker file when it runs.
Add a dependent role so it runs first
Create a /root/ans/roles/roles/baseline role (entry point /root/ans/roles/roles/baseline/tasks/main.yml) that leaves /root/ans/roles/artifacts/baseline.stamp, and declare it in dependencies of /root/ans/roles/roles/webapp/meta/main.yml.
Create the baseline role separately and list it under dependencies in meta/main.yml. The dependent role runs first.
Call the same role twice with different values
In /root/ans/roles/site.yml, call the webapp role twice. Call it once with the defaults (webapp.conf, port 8080), and once with webapp_port: 9443 so that it creates /root/ans/roles/artifacts/staging.conf. Do not copy the role directory.
In the roles section of a play, you can pass variables along with the role name. Do not copy the role.
Pass the rerun idempotency check and the lint
Save the output of the second playbook run to /root/ans/roles/out/run2.txt and the ansible-lint result to /root/ans/roles/out/lint.txt. The second run must show changed=0 and the handler must not run.
On the second run you need changed=0 and a quiet handler. Save the ansible-lint result as well.