We moved the inventory into a directory and half the groups vanished
Goal
When there are several inventory sources, you measure one by one what gets merged and what wins. At the end, you build a tool that answers "which value wins for this variable in the end".
Why it matters
When the inventory is a single file, you know it by reading it. But organizations that end up with just one file are rare — the platform team manages the common hosts and each team manages its own service hosts, production and staging are split into separate files, and cloud resources are produced by a dynamic source. Then incidents come in two kinds. One is the wrong servers entering the targets because of a same-named group, and the other is the wrong value winning because of an old file or a new group_vars. Neither can be prevented by inferring from reading files. There is only one way to prevent them — ask the machine for the merged result before running. This lab asks that question eight times, and at the end leaves a tool that asks the question for you.
Steps
- In
/root/ans/inv/src/base.ini, write thewebgroup (web1,web2) and[web:vars] tier=base, and in/root/ans/inv/src/extra.ini, write thewebgroup (web3), thedbgroup (db1), and[web:vars] tier=extra. Every host hasansible_host=127.0.0.1, and inbase.ini, under[all:vars], putansible_port=2222andansible_user=root. Giving-itwice (base.inifirst), save the merged result to/root/ans/inv/out/merged.jsonand the graph to/root/ans/inv/out/merged-graph.txt, and write whatweb1'stieris after merging into/root/ans/inv/out/two-sources.txtas the one linetier=<값>(the placeholder stands for the value). - Create
/root/ans/inv/dir/and put into it the same contents as the two files of step 1, with no extension, as10-baseand20-extra. Put one more file there,/root/ans/inv/dir/30-late.ini, which, in thewebgroup, addsweb9and writes[web:vars] tier=late. Save the result merged with-i /root/ans/inv/dirto/root/ans/inv/out/dir-merged.json, and write two lines in/root/ans/inv/out/dir-order.txt— the first line isweb1'stierastier=<값>, and the second line is the name of the file that was skipped when scanning the directory asskipped=<파일이름>(the placeholder stands for the file name). - In
/root/ans/inv/dir/group_vars/all.ymlwritepool: gv-allandowner: platform, in/root/ans/inv/dir/group_vars/web.ymlwritepool: gv-web, and in/root/ans/inv/dir/host_vars/web1.ymlwritepool: hv-web1. Then write, in/root/ans/inv/out/levels.json, thepoolvalues that the three hosts end up with as JSON with the three keysweb1,web2, anddb1. - In
/root/ans/inv/dir/20-extra, in[web:vars], addregion=inline-extra, and to/root/ans/inv/dir/group_vars/web.ymladdregion: file-web. Then write two lines in/root/ans/inv/out/inline-vs-file.txt— the first line is the valueweb2ends up with, asregion=<값>, and the second line is where that value came from, as one ofsource=inlineorsource=group_vars. - Create
/root/ans/inv/prio/hosts.ini— the hostapp1belongs to both groupsalphaandzulu, and in[alpha:vars]you writestack=from-alpha, and in[zulu:vars],stack=from-zulu. Then, in[alpha:vars], addansible_group_priority=10so thatalphawins. In/root/ans/inv/out/priority-before.txt, write the winning value without that line, and in/root/ans/inv/out/priority-after.txt, the winning value with that line, each as the one linestack=<값>. - Create
/root/ans/inv/tree/hosts.ini— in thewebgroup putweb1and in thedbgroup putdb1, and group the two groups as children ofzprod. In[zprod:vars]writeenv=from-zprodandowner=platform, and in[web:vars]writeenv=from-web. Then write three lines in/root/ans/inv/out/children.txtin alphabetical order as they are —db1.env=<값>,web1.env=<값>, andweb1.owner=<값>. - Create
/root/ans/inv/pat/hosts.ini— in thewebgroup putweb1,web2, andweb3, in thedbgroup putdb1, group the two groups as children ofprod, and in thestaginggroup putweb3anddb1. Then save only the host names that match the patternweb:&prod:!staging, one per line in alphabetical order, to/root/ans/inv/out/pattern-hosts.txt, and save the graph to/root/ans/inv/out/pat-graph.txt. - Write
/root/ans/inv/whowins.sh. It takes three arguments — an inventory path, a host name, and a variable name. It prints the value that host ends up with as the one line<변수>=<값>(variable, then value), and if that variable is not defined, it must write the reason to standard error and end with a non-zero value. With that tool, make/root/ans/inv/out/merge-report.txtin four lines — fordir,web1'stier; fordir,web1'spool; fordir,web2'sregion; and forprio/hosts.ini,app1'sstack, in that order.
Notes
- The working directory is
/root/ans/inv. This lab does not run playbooks — it uses only commands that parse the inventory. - Three questions:
--graphanswers the structure,--listanswers everything as JSON, and--hostanswers the final variables of one host. - Common mistake: putting an inventory file with an extension inside a directory and wandering, not knowing why it is not read.
- Common mistake: finding values by eye in the files and writing them down. With more than one source, your eyes will be wrong.
- Common mistake: the result of a pattern is empty but it is not an error, so the run ends in a green light with no target at all.
- How to build your inventory · Patterns · ansible-inventory command · Using variables · Ansible configuration settings
What gets merged when you give two sources
In /root/ans/inv/src/base.ini, write the web group (web1, web2) and [web:vars] tier=base, and in /root/ans/inv/src/extra.ini, write the web group (web3), the db group (db1), and [web:vars] tier=extra. Every host has ansible_host=127.0.0.1, and in base.ini, under [all:vars], put ansible_port=2222 and ansible_user=root. Giving -i twice (base.ini first), save the merged result to /root/ans/inv/out/merged.json and the graph to /root/ans/inv/out/merged-graph.txt, and write what web1's tier is after merging into /root/ans/inv/out/two-sources.txt as the one line tier=<값> (the placeholder stands for the value).
Hosts and groups become a union, and for a variable with the same name, the source read later wins. There are two commands that show the merged result, --list and --graph, and to see only one host's final variables, use --host. Don't guess the values and write them; actually ask and then write — the grader also recalculates in the same way and compares.
If you give a directory, the file names decide the order
Create /root/ans/inv/dir/ and put into it the same contents as the two files of step 1, with no extension, as 10-base and 20-extra. Put one more file there, /root/ans/inv/dir/30-late.ini, which, in the web group, adds web9 and writes [web:vars] tier=late. Save the result merged with -i /root/ans/inv/dir to /root/ans/inv/out/dir-merged.json, and write two lines in /root/ans/inv/out/dir-order.txt — the first line is web1's tier as tier=<값>, and the second line is the name of the file that was skipped when scanning the directory as skipped=<파일이름> (the placeholder stands for the file name).
The files in a directory are read in name order. But one of the three is not read — you can tell right away by looking at whether the host that file meant to add is in the result. Check the default list of the configuration item inventory_ignore_extensions with ansible-config list. Write only the file name (not the path).
The three layers made by group variables and host variables
In /root/ans/inv/dir/group_vars/all.yml write pool: gv-all and owner: platform, in /root/ans/inv/dir/group_vars/web.yml write pool: gv-web, and in /root/ans/inv/dir/host_vars/web1.yml write pool: hv-web1. Then write, in /root/ans/inv/out/levels.json, the pool values that the three hosts end up with as JSON with the three keys web1, web2, and db1.
These directories are read only if they are next to the inventory. They are layered from the broadest to the narrowest and the narrow one wins — the one that applies to everybody, the one that applies only to that group, and the one that applies only to that host. Don't infer the three values by hand; ask for each host and write them.
A group variable inside the inventory file is the weakest
In /root/ans/inv/dir/20-extra, in [web:vars], add region=inline-extra, and to /root/ans/inv/dir/group_vars/web.yml add region: file-web. Then write two lines in /root/ans/inv/out/inline-vs-file.txt — the first line is the value web2 ends up with, as region=<값>, and the second line is where that value came from, as one of source=inline or source=group_vars.
Even in the same directory, the two places differ in strength. The answer is which of inventory file group vars and inventory group_vars is lower in the official precedence list. Rather than web1, you must ask about web2, so that it is not hidden by a host variable.
Groups of the same depth go by alphabetical order, and the handle that flips it
Create /root/ans/inv/prio/hosts.ini — the host app1 belongs to both groups alpha and zulu, and in [alpha:vars] you write stack=from-alpha, and in [zulu:vars], stack=from-zulu. Then, in [alpha:vars], add ansible_group_priority=10 so that alpha wins. In /root/ans/inv/out/priority-before.txt, write the winning value without that line, and in /root/ans/inv/out/priority-after.txt, the winning value with that line, each as the one line stack=<값> (the placeholder stands for the value).
The default rule when there is no handle is the name — they are merged in alphabetical order and the later one wins. To check that value, you can make a copy without that line for a moment and ask it (leave the original as it is). This variable takes effect only if it is written inside the inventory source. If you write it in a group_vars file, nothing happens.
A child group beats a parent group
Create /root/ans/inv/tree/hosts.ini — in the web group put web1 and in the db group put db1, and group the two groups as children of zprod. In [zprod:vars] write env=from-zprod and owner=platform, and in [web:vars] write env=from-web. Then write three lines in /root/ans/inv/out/children.txt in alphabetical order as they are — db1.env=<값>, web1.env=<값>, and web1.owner=<값> (the placeholder stands for the value).
The parent's name was deliberately chosen to come later alphabetically — so that it shows the reason for winning is depth, not the name. The parent is broad and the child is narrow, and the narrow one always wins. A variable the child does not define comes down as the parent's value. Ask for all three values and write them.
Narrow the targets with a pattern and check with your own eyes
Create /root/ans/inv/pat/hosts.ini — in the web group put web1, web2, and web3, in the db group put db1, group the two groups as children of prod, and in the staging group put web3 and db1. Then save only the host names that match the pattern web:&prod:!staging, one per line in alphabetical order, to /root/ans/inv/out/pattern-hosts.txt, and save the graph to /root/ans/inv/out/pat-graph.txt.
The pattern symbols are the three: union, intersection, and difference. There is an option that prints only the target list, but the first line of its output is a count, not a host name. Whitespace also comes along, so tidy it before saving. For a difference, it is not an error even if the result is empty, so the habit of counting with your own eyes before running is important.
Which value wins for this variable in the end — build it as a tool
Write /root/ans/inv/whowins.sh. It takes three arguments — an inventory path, a host name, and a variable name. It prints the value that host ends up with as the one line <변수>=<값> (variable, then value), and if that variable is not defined, it must write the reason to standard error and end with a non-zero value. With that tool, make /root/ans/inv/out/merge-report.txt in four lines — for dir, web1's tier; for dir, web1's pool; for dir, web2's region; and for prio/hosts.ini, app1's stack, in that order.
If you answer by grepping the files, you see neither group_vars nor group priority nor child groups. Ask directly the command that gives out the merged result. To tell a missing value from a value that is an empty string, you must first check whether the key exists. The grader also runs this tool against an inventory it made itself, and looks at how it ends when asked about an undefined variable.