Skip to content

Commit

Permalink
feature: improve os detection for inventory page
Browse files Browse the repository at this point in the history
Should fix voxpupuli#485
  • Loading branch information
melck committed Sep 12, 2022
1 parent 54b7e18 commit 0b47eda
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 24 deletions.
5 changes: 3 additions & 2 deletions README.md
Expand Up @@ -122,7 +122,7 @@ The file has to be identical to
[default_settings.py](https://github.com/voxpupuli/puppetboard/blob/master/puppetboard/default_settings.py)
but should only override the settings you need changed.

If you run PuppetDB and Puppetboard on the same machine the default settings provided will be enough to get you started
If you run PuppetDB and Puppetboard on the same machine the default settings provided will be enough to get you started
and you won't need a custom settings file.

Assuming your webserver and PuppetDB machine are not identical you will at least have to change the following settings:
Expand Down Expand Up @@ -151,7 +151,7 @@ PUPPETDB_CERT="-----BEGIN CERTIFICATE-----
PUPPETDB_CERT=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQouLi4KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ==
```

For information about how to generate the correct keys please refer to the
For information about how to generate the correct keys please refer to the
[pypuppetdb documentation](https://pypuppetdb.readthedocs.io/en/latest/connecting.html#ssl). Alternatively it is possible
to explicitly specify the protocol to be used setting the `PUPPETDB_PROTO` variable.

Expand Down Expand Up @@ -186,6 +186,7 @@ Other settings that might be interesting, in no particular order:
values being unique per node, like ipaddress, uuid, and serial number, as well as structured facts it was no longer
feasible to generate a graph for everything.
- `INVENTORY_FACTS`: A list of tuples that serve as the column header and the fact name to search for to create
- `INVENTORY_FACT_TEMPLATES`: A mapping between fact name and jinja template to customize display
the inventory page. If a fact is not found for a node then `undef` is printed.
- `ENABLE_CATALOG`: If set to `True` allows the user to view a node's latest catalog. This includes all managed
resources, their file-system locations and their relationships, if available. Defaults to `False`.
Expand Down
22 changes: 21 additions & 1 deletion puppetboard/app.py
Expand Up @@ -72,7 +72,27 @@ def now(format='%m/%d/%Y %H:%M:%S'):
def version():
return __version__

return dict(now=now, version=version)
def fact_os_detection(os_facts):
os_name = ""
os_family = os_facts['family']

try:
if os_family == "windows":
os_name = os_facts["windows"]["product_name"]
elif os_family == "Darwin":
os_name = os_facts["macosx"]["product"]
else:
os_name = os_facts["distro"]["description"]
except KeyError:
pass

return os_name

return dict(
now=now,
version=version,
fact_os_detection=fact_os_detection,
)


@app.route('/offline/<path:filename>')
Expand Down
13 changes: 11 additions & 2 deletions puppetboard/default_settings.py
Expand Up @@ -43,12 +43,21 @@
'osfamily',
'puppetversion',
'processorcount']
INVENTORY_FACTS = [('Hostname', 'fqdn'),
INVENTORY_FACTS = [('Hostname', 'trusted'),
('IP Address', 'ipaddress'),
('OS', 'lsbdistdescription'),
('OS', 'os'),
('Architecture', 'hardwaremodel'),
('Kernel Version', 'kernelrelease'),
('Puppet Version', 'puppetversion'), ]

INVENTORY_FACT_TEMPLATES = {
'trusted': (
"""<a href="{{url_for('node', env=current_env, node_name=value.certname)}}">"""
"""{{value.hostname}}"""
"""</a>"""
),
'os': "{{ fact_os_detection(value) }}",
}
REFRESH_RATE = 30
DAILY_REPORTS_CHART_ENABLED = True
DAILY_REPORTS_CHART_DAYS = 8
Expand Down
24 changes: 21 additions & 3 deletions puppetboard/docker_settings.py
@@ -1,3 +1,4 @@
import json
import os
import tempfile
import base64
Expand Down Expand Up @@ -104,9 +105,9 @@ def coerce_bool(v, default):
# export INVENTORY_FACTS="Hostname, fqdn, IP Address, ipaddress,.. etc"
# Define default array of of strings, this code is a bit neater than having
# a large string
INVENTORY_FACTS_DEFAULT = ','.join(['Hostname', 'fqdn',
'IP Address', 'ipaddress',
'OS', 'lsbdistdescription',
INVENTORY_FACTS_DEFAULT = ','.join(['Hostname', 'trusted',
'IP Address', 'networking',
'OS', 'os',
'Architecture', 'hardwaremodel',
'Kernel Version', 'kernelrelease',
'Puppet Version', 'puppetversion'])
Expand All @@ -115,6 +116,23 @@ def coerce_bool(v, default):
# array: ['Key', 'Value']
INV_STR = os.getenv('INVENTORY_FACTS', INVENTORY_FACTS_DEFAULT).split(',')

# To render jinja template we expect env var to be JSON
INVENTORY_FACT_TEMPLATES = {
'trusted': (
"""<a href="{{url_for('node', env=current_env, node_name=value.certname)}}">"""
"""{{value.hostname}}"""
"""</a>"""
),
'networking': """{{ value.ip }}""",
'os': "{{ fact_os_detection(value) }}",
}

INV_TPL_STR = os.getenv('INVENTORY_FACT_TEMPLATES')

if INV_TPL_STR:
INVENTORY_FACT_TEMPLATES = json.loads(INV_TPL_STR)


# Take the Array and convert it to a tuple
INVENTORY_FACTS = [(INV_STR[i].strip(),
INV_STR[i + 1].strip()) for i in range(0, len(INV_STR), 2)]
Expand Down
16 changes: 14 additions & 2 deletions puppetboard/views/inventory.py
@@ -1,5 +1,5 @@
from flask import (
render_template, request
render_template, request, render_template_string
)
from pypuppetdb.QueryBuilder import (AndOperator,
EqualsOperator, OrOperator)
Expand Down Expand Up @@ -58,6 +58,7 @@ def inventory_ajax(env):
envs = environments()
check_env(env, envs)
headers, fact_names = inventory_facts()
fact_templates = app.config['INVENTORY_FACT_TEMPLATES']

query = AndOperator()
fact_query = OrOperator()
Expand All @@ -73,7 +74,18 @@ def inventory_ajax(env):
for fact in facts:
if fact.node not in fact_data:
fact_data[fact.node] = {}
fact_data[fact.node][fact.name] = fact.value

fact_value = fact.value

if fact.name in fact_templates:
fact_template = fact_templates[fact.name]
fact_value = render_template_string(
fact_template,
current_env=env,
value=fact_value,
)

fact_data[fact.node][fact.name] = fact_value

total = len(fact_data)

Expand Down
13 changes: 13 additions & 0 deletions test/test_docker_settings.py
Expand Up @@ -112,6 +112,19 @@ def test_invtory_facts_custom(cleanup_env):
validate_facts(docker_settings.INVENTORY_FACTS)


def test_inventory_fact_tempaltes_default(cleanup_env):
assert isinstance(docker_settings.INVENTORY_FACT_TEMPLATES, dict)
assert len(docker_settings.INVENTORY_FACT_TEMPLATES) == 3


def test_inventory_fact_tempaltes_custom(cleanup_env):
os.environ['INVENTORY_FACT_TEMPLATES'] = """{"os": "{{ fact_os_detection(value) }}"}"""
reload(docker_settings)

assert isinstance(docker_settings.INVENTORY_FACT_TEMPLATES, dict)
assert len(docker_settings.INVENTORY_FACT_TEMPLATES) == 1


def test_graph_facts_defautl(cleanup_env):
facts = docker_settings.GRAPH_FACTS
assert isinstance(facts, list)
Expand Down

0 comments on commit 0b47eda

Please sign in to comment.