1import ast
2import json
3import logging
4import os.path
5
6from flask import abort, request, url_for
7from jinja2.utils import contextfunction
8from pypuppetdb.errors import EmptyResponseError
9from requests.exceptions import ConnectionError, HTTPError
10
11
12log = logging.getLogger(__name__)
13
14
15@contextfunction
16def url_static_offline(context, value):
17    request_parts = os.path.split(os.path.dirname(context.name))
18    static_path = '/'.join(request_parts[1:])
19
20    return url_for('static', filename="%s/%s" % (static_path, value))
21
22
23def url_for_field(field, value):
24    args = request.view_args.copy()
25    args.update(request.args.copy())
26    args[field] = value
27    return url_for(request.endpoint, **args)
28
29
30def jsonprint(value):
31    return json.dumps(value, indent=2, separators=(',', ': '))
32
33
34def get_db_version(puppetdb):
35    """
36    Get the version of puppetdb.  Version form 3.2 query
37    interface is slightly different on mbeans
38    """
39    ver = ()
40    try:
41        version = puppetdb.current_version()
42        (major, minor, build) = [int(x) for x in version.split('.')]
43        ver = (major, minor, build)
44        log.info("PuppetDB Version %d.%d.%d" % (major, minor, build))
45    except ValueError:
46        log.error("Unable to determine version from string: '%s'" % puppetdb.current_version())
47        ver = (4, 2, 0)
48    except HTTPError as e:
49        log.error(str(e))
50    except ConnectionError as e:
51        log.error(str(e))
52    except EmptyResponseError as e:
53        log.error(str(e))
54    return ver
55
56
57def parse_python(value: str):
58    """
59    :param value: any string, number, bool, list or a dict
60                  casted to a string (f.e. "{'up': ['eth0'], (...)}")
61    :return: the same value but with a proper type
62    """
63    try:
64        return ast.literal_eval(value)
65    except ValueError:
66        return str(value)
67    except SyntaxError:
68        return str(value)
69
70
71def formatvalue(value):
72    if isinstance(value, str):
73        return value
74    elif isinstance(value, list):
75        return ", ".join(map(formatvalue, value))
76    elif isinstance(value, dict):
77        ret = ""
78        for k in value:
79            ret += k + " => " + formatvalue(value[k]) + ",<br/>"
80        return ret
81    else:
82        return str(value)
83
84
85def prettyprint(value):
86    html = '<table class="ui basic fixed sortable table"><thead><tr>'
87
88    # Get keys
89    for k in value[0]:
90        html += "<th>" + k + "</th>"
91
92    html += "</tr></thead><tbody>"
93
94    for e in value:
95        html += "<tr>"
96        for k in e:
97            html += "<td>" + formatvalue(e[k]) + "</td>"
98        html += "</tr>"
99
100    html += "</tbody></table>"
101    return html
102
103
104def get_or_abort(func, *args, **kwargs):
105    """Execute the function with its arguments and handle the possible
106    errors that might occur.
107
108    In this case, if we get an exception we simply abort the request.
109    """
110    try:
111        return func(*args, **kwargs)
112    except HTTPError as e:
113        log.error(str(e))
114        abort(e.response.status_code)
115    except ConnectionError as e:
116        log.error(str(e))
117        abort(500)
118    except EmptyResponseError as e:
119        log.error(str(e))
120        abort(204)
121    except Exception as e:
122        log.error(str(e))
123        abort(500)
124
125
126def yield_or_stop(generator):
127    """Similar in intent to get_or_abort this helper will iterate over our
128    generators and handle certain errors.
129
130    Since this is also used in streaming responses where we can't just abort
131    a request we raise StopIteration.
132    """
133    while True:
134        try:
135            yield next(generator)
136        except (EmptyResponseError, ConnectionError, HTTPError, StopIteration):
137            return
138