Skip to content

Commit c6ca394

Browse files
committed
Support for template fragments in custom columns.
1 parent 1a441cd commit c6ca394

File tree

6 files changed

+153
-59
lines changed

6 files changed

+153
-59
lines changed

docs/usage.md

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ The `sql` template generates an .sql file that can be loaded into an SQLite or
196196
MySQL database.
197197

198198
$ ansible-cmdb -t sql -i hosts out > cmdb.sql
199-
$ echo "CREATE DATABASE ansiblecmdb" | mysql
199+
$ echo "CREATE DATABASE ansiblecmdb" | mysql
200200
$ mysql ansiblecmdb < cmdb.sql
201201

202202
## Fact caching
@@ -432,54 +432,98 @@ The software items will be listed under the "*Custom facts*" heading.
432432

433433
You can add custom columns to the host overview with the `-C` (`--cust-cols`)
434434
option. This allows you to specify
435-
[jsonxs](https://github.com/fboender/jsonxs) expressions to extract and
436-
display custom host facts. Such columns are fairly limited in what they can
437-
display. If you need a more powerful method of adding custom data to your
438-
CMDB, please refer to the [Custom templates](#custom-templates) section.
435+
[jsonxs](https://github.com/fboender/jsonxs) expressions or [Mako
436+
template](https://www.makotemplates.org/) fragments to extract and
437+
display custom host facts.
439438

440439
Custom columns are currently only supported by the `html_fancy` and
441-
`html_fancy_split` templates!
440+
`html_fancy_split` templates.
442441

443-
The `-C` option takes a parameter which is the path to a JSON file containing
444-
your custom column definitions. An example can be found in the
445-
`examples/cust_cols.json` file in the repo:
442+
The `-C` option takes a parameter which is the path to a file containing
443+
your custom column definitions. The file's syntax is Python (even though it
444+
looks like JSON). An example can be found in the
445+
`examples/cust_cols.conf` file in the repo:
446446

447447
[
448+
# Show whether AppArmor is enabled
448449
{
449450
"title": "AppArmor",
450451
"id": "apparmor",
451452
"sType": "string",
452-
"visible": true,
453+
"visible": False,
453454
"jsonxs": "ansible_facts.ansible_apparmor.status"
454455
},
456+
# Show the nameservers configured on the host
455457
{
456-
"title": "Proc type",
457-
"id": "proctype",
458+
"title": "Nameservers",
459+
"id": "nameservers",
458460
"sType": "string",
459-
"visible": true,
460-
"jsonxs": "ansible_facts.ansible_processor[2]"
461+
"visible": True,
462+
"tpl": """
463+
<ul>
464+
<%
465+
# Get ansible_facts.ansible_dns.nameservers
466+
facts = host.get('ansible_facts', {})
467+
dns = facts.get('ansible_dns', {})
468+
nameservers = dns.get('nameservers', [])
469+
%>
470+
% for nameserver in nameservers:
471+
<li>${nameserver}</li>
472+
% endfor
473+
</ul>
474+
"""
475+
},
476+
# Show the nameservers configured on the host, but use jsonxs.
477+
{
478+
"title": "Nameservers2",
479+
"id": "nameservers2",
480+
"sType": "string",
481+
"visible": True,
482+
"tpl": """
483+
<ul>
484+
<%
485+
# Get ansible_facts.ansible_dns.nameservers using jsonxs
486+
nameservers = jsonxs(host, 'ansible_facts.ansible_dns.nameservers', default=[])
487+
%>
488+
% for nameserver in nameservers:
489+
<li>${nameserver}</li>
490+
% endfor
491+
</ul>
492+
"""
461493
}
462494
]
463495

464-
This defines two new columns: 'AppArmor' and 'Proc type'. All keys are
465-
required.
496+
This defines two new columns: 'AppArmor' and 'Nameservers'. Each column
497+
consist of the following key/values:
466498

467-
* `title` is what is displayed as the columns user-friendly title.
468-
* The `id` key must have a unique value, to differentiate between
469-
columns.
499+
* `title` is what is displayed as the columns user-friendly title. **Required**.
500+
* The `id` key must have a unique value, to differentiate between columns.
501+
**Required**
470502
* The `sType` value determines how the values will be sorted in the host
471-
overview. Possible values include `string` and `num`.
503+
overview. Possible values include `string` and `num`. **Required**
472504
* `visible` determines whether the column will be active (shown) by default.
473-
* The `jsonxs` expression points to an entry in the facts files for each host,
474-
and determines what will be shown for the column's value for each host.
475-
The easiest way to figure out a jsonxs expression is by opening one of the
476-
gathered facts files in a json editor. Please see
505+
**Required**
506+
* The `jsonxs` expression, if specified points to an entry in the facts files
507+
for each host, and determines what will be shown for the column's value for
508+
each host. The easiest way to figure out a jsonxs expression is by opening
509+
one of the gathered facts files in a json editor. Please see
477510
[jsonxs](https://github.com/fboender/jsonxs) for info on how to write jsonxs
478-
expressions.
511+
expressions. **Optional**
512+
* The `tpl` expression, if specified, is a [Mako](http://www.makotemplates.org/)
513+
template fragment. A single variable `host` is made available in this
514+
template. Care must be taken when accessing host information. If one of the
515+
hosts is missing the information you're trying to access, the template will
516+
not render and ansible-cmdb will crash (usually with a 'KeyError' message).
517+
You should always use the `get()` method and specify a default value. E.g.
518+
`host.get('ansible_facts', {}).get('ansible_dns', {}).get('nameservers',
519+
[])`. Alternatively (and recommended) is that you use `jsonxs` to access
520+
your info (and specify `default=...`). See the example above. **Optional**
521+
522+
Either `jsonxs` or `tpl` is required.
479523

480524
To use it:
481525

482-
../ansible-cmdb/src/ansible-cmdb -C example/cust_cols.json -i example/hosts example/out/ > cmdb.html
526+
ansible-cmdb -C example/cust_cols.conf -i example/hosts example/out/ > cmdb.html
483527

484528
When opening the `cmdb.html` file in your browser, you may have to hit the
485529
'Clear settings' button in the top-right before the new columns show up or
@@ -488,14 +532,13 @@ when you get strange behaviour.
488532

489533
## Custom templates
490534

491-
Custom columns can be added with the `-C` param. See the [Custom
492-
columns](#custom-columns) section for more info. Custom columns are somewhat
493-
limited in the type of information they can display (basically only strings
494-
and numbers). If you want to add more elaborate custom columns or other data
495-
to the output, you can create a custom template. Ansible-cmdb uses the [Mako
496-
templating engine](http://www.makotemplates.org/) to render output.
535+
It's possible to create custom templates to build completely different CMDBs
536+
or to enhance the existing ones. Ansible-cmdb uses the [Mako templating
537+
engine](http://www.makotemplates.org/) to render output.
497538

498-
For example, if you want to add a custom column to the `html_fancy` template:
539+
For example, if you want to add a custom column to the `html_fancy` template
540+
(note that it's easier to just use the `--cust-cols` option. For more info see
541+
above):
499542

500543
1. Make a copy of the default `html_fancy` template in a new dir. Here, we'll
501544
use files from the ansible-cmdb git repository.

example/cust_cols.conf

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[
2+
# Show whether AppArmor is enabled
3+
{
4+
"title": "AppArmor",
5+
"id": "apparmor",
6+
"sType": "string",
7+
"visible": False,
8+
"jsonxs": "ansible_facts.ansible_apparmor.status"
9+
},
10+
# Show the nameservers configured on the host
11+
{
12+
"title": "Nameservers",
13+
"id": "nameservers",
14+
"sType": "string",
15+
"visible": True,
16+
"tpl": """
17+
<ul>
18+
<%
19+
# Get ansible_facts.ansible_dns.nameservers
20+
facts = host.get('ansible_facts', {})
21+
dns = facts.get('ansible_dns', {})
22+
nameservers = dns.get('nameservers', [])
23+
%>
24+
% for nameserver in nameservers:
25+
<li>${nameserver}</li>
26+
% endfor
27+
</ul>
28+
"""
29+
},
30+
# Show the nameservers configured on the host, but use jsonxs.
31+
{
32+
"title": "Nameservers2",
33+
"id": "nameservers2",
34+
"sType": "string",
35+
"visible": True,
36+
"tpl": """
37+
<ul>
38+
<%
39+
# Get ansible_facts.ansible_dns.nameservers using jsonxs
40+
nameservers = jsonxs(host, 'ansible_facts.ansible_dns.nameservers', default=[])
41+
%>
42+
% for nameserver in nameservers:
43+
<li>${nameserver}</li>
44+
% endfor
45+
</ul>
46+
"""
47+
48+
}
49+
]

example/cust_cols.json

Lines changed: 0 additions & 16 deletions
This file was deleted.

example/generate.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ python2 ../src/ansible-cmdb.py -q -t markdown -i hosts out > gen_markdown_2.md
2020
python2 ../src/ansible-cmdb.py -q -t sql -i hosts out > gen_sql_2.md
2121
python2 ../src/ansible-cmdb.py -q -t html_fancy_split -i hosts out
2222
python2 ../src/ansible-cmdb.py -q -i hosts -f out_factcache > gen_fact_cache_2.html
23-
python2 ../src/ansible-cmdb.py -q -i hosts -C cust_cols.json out > gen_fact_cust_cols_2.html
23+
python2 ../src/ansible-cmdb.py -q -i hosts -C cust_cols.conf out > gen_fact_cust_cols_2.html
2424

2525

2626
# Python v3
@@ -32,4 +32,4 @@ python3 ../src/ansible-cmdb.py -q -t markdown -i hosts out > gen_markdown_3.md
3232
python3 ../src/ansible-cmdb.py -q -t sql -i hosts out > gen_sql_3.md
3333
python3 ../src/ansible-cmdb.py -q -t html_fancy_split -i hosts out
3434
python3 ../src/ansible-cmdb.py -q -i hosts -f out_factcache > gen_fact_cache_3.html
35-
python3 ../src/ansible-cmdb.py -q -i hosts -C cust_cols.json out > gen_fact_cust_cols_2.html
35+
python3 ../src/ansible-cmdb.py -q -i hosts -C cust_cols.conf out > gen_fact_cust_cols_2.html

src/ansible-cmdb.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import sys
1616
import os
1717
import logging
18-
import json
18+
import ast
1919
from mako import exceptions
2020
import ansiblecmdb
2121
import ansiblecmdb.util as util
@@ -97,14 +97,14 @@ def get_cust_cols(path):
9797
"""
9898
Load custom column definitions.
9999
"""
100-
required_keys = ["title", "id", "sType", "visible", "jsonxs"]
100+
required_keys = ["title", "id", "sType", "visible"]
101101

102102
with open(path, 'r') as f:
103103
try:
104-
cust_cols = json.load(f)
105-
except json.decoder.JSONDecodeError as err:
106-
sys.stderr.write("Invalid custom columns json file: {}\n".format(path))
107-
sys.stderr.write("{}\n".format(err.args[0]))
104+
cust_cols = ast.literal_eval(f.read())
105+
except Exception as err:
106+
sys.stderr.write("Invalid custom columns file: {}\n".format(path))
107+
sys.stderr.write("{}\n".format(err))
108108
sys.exit(1)
109109

110110
# Validate
@@ -114,6 +114,12 @@ def get_cust_cols(path):
114114
sys.stderr.write("Missing required key '{}' in custom "
115115
"column {}\n".format(required_key, col))
116116
sys.exit(1)
117+
if "jsonxs" not in col and "tpl" not in col:
118+
sys.stderr.write("You need to specify 'jsonxs' or 'tpl' "
119+
"for custom column {}\n".format(col))
120+
sys.exit(1)
121+
122+
117123

118124
return cust_cols
119125

src/ansiblecmdb/data/tpl/html_fancy_defs.html

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<%! import socket %>
66
<%! import getpass %>
77
<%! from ansiblecmdb.util import to_bool %>
8+
<%! from mako.template import Template %>
89

910

1011
##
@@ -13,6 +14,10 @@
1314

1415
<%def name="var_cols(cols_visible=None, cols_exclude=None)">
1516
<%
17+
# These are the baked in columns. The user can also extend these columns
18+
# through --cust-cols, in which case those columns are appended to this list
19+
# (in the calling template file).
20+
1621
cols = [
1722
{"title": "Name", "id": "name", "func": col_name, "sType": "string", "visible": True},
1823
{"title": "Groups", "id": "groups", "func": col_groups, "sType": "string", "visible": False},
@@ -282,7 +287,9 @@ <h2>Host overview</h2>
282287
% if "func" in col:
283288
<td>${col["func"](host, **kwargs)}</td>\
284289
% elif "jsonxs" in col:
285-
<td>${col__cust(host, col=col, **kwargs)}</td>\
290+
<td>${col__cust_jsonxs(host, col=col, **kwargs)}</td>\
291+
% elif "tpl" in col:
292+
<td>${col__cust_tpl(host, col=col, **kwargs)}</td>\
286293
% endif
287294
% endfor
288295
</tr>
@@ -473,11 +480,16 @@ <h3 class="toggle-collapse ${collapsed_class}" id="${host['name']}" data-host-na
473480
## in the function scope in Mako for some reason, so if we need to use them, we
474481
## need to do a `kwargs.get()`. I don't like Mako much.
475482

476-
<%def name="col__cust(host, col, **kwargs)">
483+
<%def name="col__cust_jsonxs(host, col, **kwargs)">
477484
## Special column function for custom columns that does a jsonxs lookup
478485
${jsonxs(host, col["jsonxs"], default='')}
479486
</%def>
480487

488+
<%def name="col__cust_tpl(host, col, **kwargs)">
489+
## Special column function for custom columns that renders a string template
490+
${Template(col["tpl"]).render(host=host, jsonxs=jsonxs)}
491+
</%def>
492+
481493
<%def name="col_name(host, **kwargs)">
482494
<%
483495
link_type = kwargs.get("link_type")

0 commit comments

Comments
 (0)