]>
Commit | Line | Data |
---|---|---|
e306af50 | 1 | import copy |
f91f0fd5 | 2 | from typing import Optional, TYPE_CHECKING |
e306af50 | 3 | |
f91f0fd5 | 4 | from jinja2 import Environment, PackageLoader, select_autoescape, StrictUndefined, Template |
e306af50 TL |
5 | from jinja2 import exceptions as j2_exceptions |
6 | ||
f91f0fd5 TL |
7 | if TYPE_CHECKING: |
8 | from cephadm.module import CephadmOrchestrator | |
9 | ||
e306af50 TL |
10 | |
11 | class TemplateError(Exception): | |
12 | pass | |
13 | ||
14 | ||
15 | class UndefinedError(TemplateError): | |
16 | pass | |
17 | ||
18 | ||
19 | class TemplateNotFoundError(TemplateError): | |
20 | pass | |
21 | ||
22 | ||
23 | class TemplateEngine: | |
24 | def render(self, name: str, context: Optional[dict] = None) -> str: | |
25 | raise NotImplementedError() | |
26 | ||
27 | ||
28 | class Jinja2Engine(TemplateEngine): | |
29 | def __init__(self): | |
30 | self.env = Environment( | |
31 | loader=PackageLoader('cephadm', 'templates'), | |
f91f0fd5 | 32 | autoescape=select_autoescape(['html', 'xml'], default_for_string=False), |
e306af50 TL |
33 | trim_blocks=True, |
34 | lstrip_blocks=True, | |
35 | undefined=StrictUndefined | |
36 | ) | |
37 | ||
38 | def render(self, name: str, context: Optional[dict] = None) -> str: | |
39 | try: | |
40 | template = self.env.get_template(name) | |
41 | if context is None: | |
42 | return template.render() | |
43 | return template.render(context) | |
44 | except j2_exceptions.UndefinedError as e: | |
45 | raise UndefinedError(e.message) | |
46 | except j2_exceptions.TemplateNotFound as e: | |
47 | raise TemplateNotFoundError(e.message) | |
48 | ||
f91f0fd5 TL |
49 | def render_plain(self, source, context): |
50 | try: | |
51 | template = self.env.from_string(source) | |
52 | if context is None: | |
53 | return template.render() | |
54 | return template.render(context) | |
55 | except j2_exceptions.UndefinedError as e: | |
56 | raise UndefinedError(e.message) | |
57 | except j2_exceptions.TemplateNotFound as e: | |
58 | raise TemplateNotFoundError(e.message) | |
59 | ||
e306af50 TL |
60 | |
61 | class TemplateMgr: | |
f91f0fd5 | 62 | def __init__(self, mgr: "CephadmOrchestrator"): |
e306af50 TL |
63 | self.engine = Jinja2Engine() |
64 | self.base_context = { | |
65 | 'cephadm_managed': 'This file is generated by cephadm.' | |
66 | } | |
f91f0fd5 | 67 | self.mgr = mgr |
e306af50 | 68 | |
f91f0fd5 TL |
69 | def render(self, name: str, |
70 | context: Optional[dict] = None, | |
71 | managed_context=True, | |
72 | host: Optional[str] = None) -> str: | |
e306af50 TL |
73 | """Render a string from a template with context. |
74 | ||
75 | :param name: template name. e.g. services/nfs/ganesha.conf.j2 | |
76 | :type name: str | |
77 | :param context: a dictionary that contains values to be used in the template, defaults | |
78 | to None | |
79 | :type context: Optional[dict], optional | |
80 | :param managed_context: to inject default context like managed header or not, defaults | |
81 | to True | |
82 | :type managed_context: bool, optional | |
f91f0fd5 TL |
83 | :param host: The host name used to build the key to access |
84 | the module's persistent key-value store. | |
85 | :type host: Optional[str], optional | |
e306af50 TL |
86 | :return: the templated string |
87 | :rtype: str | |
88 | """ | |
89 | ctx = {} | |
90 | if managed_context: | |
91 | ctx = copy.deepcopy(self.base_context) | |
92 | if context is not None: | |
93 | ctx = {**ctx, **context} | |
f91f0fd5 TL |
94 | |
95 | # Check if the given name exists in the module's persistent | |
96 | # key-value store, e.g. | |
97 | # - blink_device_light_cmd | |
98 | # - <host>/blink_device_light_cmd | |
99 | # - services/nfs/ganesha.conf | |
100 | store_name = name.rstrip('.j2') | |
101 | custom_template = self.mgr.get_store(store_name, None) | |
102 | if host and custom_template is None: | |
103 | store_name = '{}/{}'.format(host, store_name) | |
104 | custom_template = self.mgr.get_store(store_name, None) | |
105 | ||
106 | if custom_template: | |
107 | return self.engine.render_plain(custom_template, ctx) | |
108 | else: | |
109 | return self.engine.render(name, ctx) |