1 #!/usr/bin/python3 2 3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC") 4 # 5 # SPDX-License-Identifier: MPL-2.0 6 # 7 # This Source Code Form is subject to the terms of the Mozilla Public 8 # License, v. 2.0. If a copy of the MPL was not distributed with this 9 # file, you can obtain one at https://mozilla.org/MPL/2.0/. 10 # 11 # See the COPYRIGHT file distributed with this work for additional 12 # information regarding copyright ownership. 13 14 from dataclasses import dataclass 15 from pathlib import Path 16 from typing import Any, Dict, Optional, Union 17 18 import jinja2 19 20 from .log import debug 21 from .vars import ALL 22 23 24 class TemplateEngine: 25 """ 26 Engine for rendering jinja2 templates in system test directories. 27 """ 28 29 def __init__(self, directory: Union[str, Path], env_vars=ALL): 30 """ 31 Initialize the template engine for `directory`, optionally overriding 32 the `env_vars` that will be used when rendering the templates (defaults 33 to the environment variables set by the pytest runner). 34 """ 35 self.directory = Path(directory) 36 self.env_vars = dict(env_vars) 37 self.j2env = jinja2.Environment( 38 loader=jinja2.FileSystemLoader(str(self.directory)), 39 undefined=jinja2.StrictUndefined, 40 variable_start_string="@", 41 variable_end_string="@", 42 ) 43 44 def render( 45 self, 46 output: str, 47 data: Optional[Dict[str, Any]] = None, 48 template: Optional[str] = None, 49 ) -> None: 50 """ 51 Render `output` file from jinja `template` and fill in the `data`. The 52 `template` defaults to *.j2.manual or *.j2 file. The environment 53 variables which the engine was initialized with are also filled in. In 54 case of a variable name clash, `data` has precedence. 55 """ 56 if template is None: 57 template = f"{output}.j2.manual" 58 if not Path(template).is_file(): 59 template = f"{output}.j2" 60 if not Path(template).is_file(): 61 raise RuntimeError('No jinja2 template found for "{output}"') 62 63 if data is None: 64 data = self.env_vars 65 else: 66 data = {**self.env_vars, **data} 67 68 debug("rendering template `%s` to file `%s`", template, output) 69 stream = self.j2env.get_template(template).stream(data) 70 stream.dump(output, encoding="utf-8") 71 72 def render_auto(self, data: Optional[Dict[str, Any]] = None): 73 """ 74 Render all *.j2 templates with default (and optionally the provided) 75 values and write the output to files without the .j2 extensions. 76 """ 77 templates = [ 78 str(filepath.relative_to(self.directory)) 79 for filepath in self.directory.rglob("*.j2") 80 ] 81 for template in templates: 82 self.render(template[:-3], data) 83 84 85 @dataclass 86 class Nameserver: 87 name: str 88 ip: str 89 90 91 @dataclass 92 class Zone: 93 name: str 94 filename: str 95 ns: Nameserver 96 type: str = "primary" 97 98 99 @dataclass 100 class TrustAnchor: 101 domain: str 102 type: str 103 contents: str 104