annet 0.10__py3-none-any.whl → 0.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

@@ -1,221 +1,53 @@
1
1
  from __future__ import annotations
2
2
 
3
- import abc
4
- import contextlib
5
3
  import dataclasses
6
4
  import importlib
7
5
  import os
8
- import pkgutil
9
- import re
10
6
  import textwrap
11
- import time
12
- import types
13
7
  from collections import OrderedDict as odict
14
8
  from typing import (
15
- Any,
16
- Callable,
17
- Dict,
18
9
  FrozenSet,
19
10
  Iterable,
20
11
  List,
21
12
  Optional,
22
- Set,
23
- Tuple,
24
13
  Union,
25
14
  )
26
15
 
27
- from annet.annlib import jsontools
28
16
  from annet.annlib.rbparser.acl import compile_acl_text
29
17
  from contextlog import get_logger
30
18
 
31
- from annet.storage import Device, Storage
19
+ from annet.storage import Device
32
20
 
33
21
  from annet import patching, tabparser, tracing
34
22
  from annet.cli_args import GenSelectOptions, ShowGeneratorsOptions
35
23
  from annet.lib import (
36
- add_annotation,
37
- flatten,
38
24
  get_context,
39
- jinja_render,
40
- mako_render,
41
- merge_dicts,
42
25
  )
43
- from annet.reference import RefMatcher, RefTracker
44
26
  from annet.tracing import tracing_connector
45
27
  from annet.types import (
46
28
  GeneratorEntireResult,
47
29
  GeneratorJSONFragmentResult,
48
30
  GeneratorPartialResult,
49
31
  GeneratorPartialRunArgs,
50
- GeneratorPerf,
51
32
  GeneratorResult,
52
33
  )
53
-
34
+ from .base import (
35
+ BaseGenerator,
36
+ TextGenerator as TextGenerator,
37
+ ParamsList as ParamsList,
38
+ )
39
+ from .exceptions import NotSupportedDevice, GeneratorError
40
+ from .jsonfragment import JSONFragment
41
+ from .partial import PartialGenerator
42
+ from .entire import Entire
43
+ from .ref import RefGenerator
44
+ from .perf import GeneratorPerfMesurer
45
+ from .result import RunGeneratorResult
54
46
 
55
47
  # =====
56
48
  DISABLED_TAG = "disable"
57
49
 
58
50
 
59
- # =====
60
- class GeneratorError(Exception):
61
- pass
62
-
63
-
64
- class NotSupportedDevice(GeneratorError):
65
- pass
66
-
67
-
68
- class DefaultBlockIfCondition:
69
- pass
70
-
71
-
72
- class GeneratorPerfMesurer:
73
- def __init__(
74
- self,
75
- gen: Union["PartialGenerator", "Entire"],
76
- storage: Storage,
77
- run_args: Optional[GeneratorPartialRunArgs] = None,
78
- trace_min_duration: tracing.MinDurationT = None
79
- ):
80
- self._gen = gen
81
- self._storage = storage
82
- self._run_args = run_args
83
-
84
- self._start_time: float = 0.0
85
- self._span_ctx = None
86
- self._span = None
87
- self._trace_min_duration = trace_min_duration
88
-
89
- self.last_result: Optional[GeneratorPerf] = None
90
-
91
- def start(self) -> None:
92
- self.last_result = None
93
-
94
- self._storage.flush_perf()
95
-
96
- self._span_ctx = tracing_connector.get().start_as_current_span(
97
- "gen:call",
98
- tracer_name=self._gen.__class__.__module__,
99
- min_duration=self._trace_min_duration,
100
- )
101
- self._span = self._span_ctx.__enter__() # pylint: disable=unnecessary-dunder-call
102
-
103
- if self._span:
104
- self._span.set_attributes({"generator.class": self._gen.__class__.__name__})
105
- if self._run_args:
106
- tracing_connector.get().set_device_attributes(self._span, self._run_args.device)
107
-
108
- self._start_time = time.monotonic()
109
-
110
- def finish(self, exc_type=None, exc_val=None, exc_tb=None) -> GeneratorPerf:
111
- total = time.monotonic() - self._start_time
112
- rt = self._storage.flush_perf()
113
- self._span_ctx.__exit__(exc_type, exc_val, exc_tb)
114
-
115
- meta = {}
116
- if tracing_connector.get().enabled:
117
- span_context = self._span.get_span_context()
118
- meta = {
119
- "span": {
120
- "trace_id": str(span_context.trace_id),
121
- "span_id": str(span_context.span_id),
122
- }
123
- }
124
-
125
- self.last_result = GeneratorPerf(total=total, rt=rt, meta=meta)
126
- return self.last_result
127
-
128
- def __enter__(self):
129
- self.start()
130
- return self
131
-
132
- def __exit__(self, exc_type, exc_val, exc_tb):
133
- self.finish(exc_type, exc_val, exc_tb)
134
-
135
-
136
- class RunGeneratorResult:
137
- """
138
- Результат запуска run_partial_generators/run_file_generators
139
- """
140
-
141
- def __init__(self):
142
- self.partial_results: Dict[str, GeneratorPartialResult] = {}
143
- self.entire_results: Dict[str, GeneratorEntireResult] = {}
144
- self.json_fragment_results: Dict[str, GeneratorJSONFragmentResult] = {}
145
- self.ref_track: RefTracker = RefTracker()
146
- self.ref_matcher: RefMatcher = RefMatcher()
147
-
148
- def add_partial(self, result: GeneratorPartialResult):
149
- self.partial_results[result.name] = result
150
-
151
- def add_entire(self, result: GeneratorEntireResult) -> None:
152
- # Если есть несколько генераторов на один файл, выбрать тот, что с большим приоритетом
153
- if result.path:
154
- if result.path not in self.entire_results or result.prio > self.entire_results[result.path].prio:
155
- self.entire_results[result.path] = result
156
-
157
- def add_json_fragment(self, result: GeneratorJSONFragmentResult) -> None:
158
- self.json_fragment_results[result.name] = result
159
-
160
- def config_tree(self, safe: bool = False) -> Dict[str, Any]: # OrderedDict
161
- tree = odict()
162
- for gr in self.partial_results.values():
163
- config = gr.safe_config if safe else gr.config
164
- tree = merge_dicts(tree, config)
165
- return tree
166
-
167
- def new_files(self, safe: bool = False) -> Dict[str, Tuple[str, str]]:
168
- files = {}
169
- for gr in self.entire_results.values():
170
- if not safe or gr.is_safe:
171
- files[gr.path] = (gr.output, gr.reload)
172
- return files
173
-
174
- def acl_text(self) -> str:
175
- return _combine_acl_text(self.partial_results, lambda gr: gr.acl)
176
-
177
- def acl_safe_text(self) -> str:
178
- return _combine_acl_text(self.partial_results, lambda gr: gr.acl_safe)
179
-
180
- def new_json_fragment_files(
181
- self,
182
- old_files: Dict[str, Optional[str]],
183
- safe: bool = False,
184
- ) -> Dict[str, Tuple[Any, Optional[str]]]:
185
- files: Dict[str, Tuple[Any, Optional[str]]] = {}
186
- reload_prios: Dict[str, int] = {}
187
- for generator_result in self.json_fragment_results.values():
188
- filepath = generator_result.path
189
- if filepath not in files:
190
- if old_files.get(filepath) is not None:
191
- files[filepath] = (old_files[filepath], None)
192
- else:
193
- files[filepath] = ({}, None)
194
- previous_config: Dict[str, Any] = files[filepath][0]
195
- new_fragment = generator_result.config
196
-
197
- if safe:
198
- new_config = jsontools.apply_json_fragment(previous_config, new_fragment, generator_result.acl_safe)
199
- else:
200
- new_config = jsontools.apply_json_fragment(previous_config, new_fragment, generator_result.acl)
201
-
202
- if filepath in reload_prios and reload_prios[filepath] > generator_result.reload_prio:
203
- _, reload_cmd = files[filepath]
204
- else:
205
- reload_cmd = generator_result.reload
206
- reload_prios[filepath] = generator_result.reload_prio
207
- files[filepath] = (new_config, reload_cmd)
208
- return files
209
-
210
- def perf_mesures(self) -> Dict[str, Dict[str, int]]:
211
- mesures = {}
212
- for gr in self.partial_results.values():
213
- mesures[gr.name] = {"total": gr.perf.total, "rt": gr.perf.rt, "meta": gr.perf.meta}
214
- for gr in self.entire_results.values():
215
- mesures[gr.name] = {"total": gr.perf.total, "rt": gr.perf.rt, "meta": gr.perf.meta}
216
- return mesures
217
-
218
-
219
51
  # =====
220
52
  def get_list(args: ShowGeneratorsOptions):
221
53
  if args.generators_context is not None:
@@ -257,6 +89,7 @@ class Generators:
257
89
 
258
90
  partial: List[PartialGenerator] = dataclasses.field(default_factory=list)
259
91
  entire: List[Entire] = dataclasses.field(default_factory=list)
92
+ ref: List[RefGenerator] = dataclasses.field(default_factory=list)
260
93
  json_fragment: List[JSONFragment] = dataclasses.field(default_factory=list)
261
94
 
262
95
 
@@ -265,35 +98,46 @@ def build_generators(storage, gens: GenSelectOptions, device: Optional[Device] =
265
98
  if gens.generators_context is not None:
266
99
  os.environ["ANN_GENERATORS_CONTEXT"] = gens.generators_context
267
100
  all_generators = _get_generators(get_context()["generators"], storage, device)
101
+ ref_generators = _get_ref_generators(get_context()["generators"], storage, device)
268
102
  validate_genselect(gens, all_generators)
269
103
  classes = list(select_generators(gens, all_generators))
270
104
  partial = [obj for obj in classes if obj.TYPE == "PARTIAL"]
271
105
  entire = [obj for obj in classes if obj.TYPE == "ENTIRE"]
272
106
  entire = list(sorted(entire, key=lambda x: x.prio, reverse=True))
273
107
  json_fragment = [obj for obj in classes if obj.TYPE == "JSON_FRAGMENT"]
274
- return Generators(partial=partial, entire=entire, json_fragment=json_fragment)
108
+ return Generators(
109
+ partial=partial,
110
+ entire=entire,
111
+ json_fragment=json_fragment,
112
+ ref=ref_generators,
113
+ )
275
114
 
276
115
 
277
116
  @tracing.function
278
- def run_partial_initial(device, storage):
117
+ def run_partial_initial(device):
279
118
  from .common.initial import InitialConfig
280
119
 
281
120
  tracing_connector.get().set_device_attributes(tracing_connector.get().get_current_span(), device)
282
121
 
283
- run_args = GeneratorPartialRunArgs(device, storage)
284
- return run_partial_generators([InitialConfig()], run_args)
122
+ run_args = GeneratorPartialRunArgs(device)
123
+ return run_partial_generators([InitialConfig(storage=device.storage)], [], run_args)
285
124
 
286
125
 
287
126
  @tracing.function
288
- def run_partial_generators(gens: List["PartialGenerator"], run_args: GeneratorPartialRunArgs):
127
+ def run_partial_generators(
128
+ gens: List["PartialGenerator"],
129
+ ref_gens: List["RefGenerator"],
130
+ run_args: GeneratorPartialRunArgs,
131
+ ):
289
132
  logger = get_logger(host=run_args.device.hostname)
290
133
  tracing_connector.get().set_device_attributes(tracing_connector.get().get_current_span(), run_args.device)
291
134
 
292
135
  ret = RunGeneratorResult()
293
136
  if run_args.generators_context is not None:
294
137
  os.environ["ANN_GENERATORS_CONTEXT"] = run_args.generators_context
295
- for gen in _get_ref_generators(get_context()["generators"], run_args.storage):
296
- ret.ref_matcher.add(gen.ref(run_args.device), gen.__class__)
138
+
139
+ for gen in ref_gens:
140
+ ret.ref_matcher.add(gen.ref(run_args.device), gen)
297
141
 
298
142
  logger.debug("Generating selected PARTIALs ...")
299
143
 
@@ -310,9 +154,9 @@ def run_partial_generators(gens: List["PartialGenerator"], run_args: GeneratorPa
310
154
  config = result.safe_config if run_args.use_acl_safe else result.config
311
155
 
312
156
  ref_match = ret.ref_matcher.match(config)
313
- for gen_cls, groups in ref_match:
314
- gens.append(gen_cls(run_args.storage, groups))
315
- ret.ref_track.add(gen.__class__, gen_cls)
157
+ for ref_gen, groups in ref_match:
158
+ gens.append(ref_gen.with_groups(groups))
159
+ ret.ref_track.add(gen.__class__, ref_gen.__class__)
316
160
 
317
161
  ret.ref_track.config(gen.__class__, config)
318
162
  ret.add_partial(result)
@@ -342,7 +186,7 @@ def _run_partial_generator(gen: "PartialGenerator", run_args: GeneratorPartialRu
342
186
  "generators_context": str(run_args.generators_context),
343
187
  })
344
188
 
345
- with GeneratorPerfMesurer(gen, run_args.storage, run_args=run_args) as pm:
189
+ with GeneratorPerfMesurer(gen, run_args=run_args) as pm:
346
190
  if not run_args.no_new:
347
191
  if gen.get_user_runner(device):
348
192
  logger.info("Generating PARTIAL ...")
@@ -430,7 +274,6 @@ def check_entire_generators_required_packages(gens, device_packages: FrozenSet[s
430
274
  def run_file_generators(
431
275
  gens: Iterable[Union["JSONFragment", "Entire"]],
432
276
  device: "Device",
433
- storage: Storage,
434
277
  ) -> RunGeneratorResult:
435
278
  """Run generators that generate files or file parts."""
436
279
  ret = RunGeneratorResult()
@@ -446,7 +289,7 @@ def run_file_generators(
446
289
  else:
447
290
  raise RuntimeError(f"Unknown generator class type: cls={gen.__class__} TYPE={gen.__class__.TYPE}")
448
291
  try:
449
- result = run_generator_fn(gen, device, storage)
292
+ result = run_generator_fn(gen, device)
450
293
  except NotSupportedDevice as exc:
451
294
  logger.info("generator %s raised unsupported error: %r", gen, exc)
452
295
  continue
@@ -457,7 +300,7 @@ def run_file_generators(
457
300
 
458
301
 
459
302
  @tracing.function(min_duration="0.5")
460
- def _run_entire_generator(gen: "Entire", device: "Device", storage: Storage) -> Optional[GeneratorResult]:
303
+ def _run_entire_generator(gen: "Entire", device: "Device") -> Optional[GeneratorResult]:
461
304
  logger = get_logger(generator=_make_generator_ctx(gen))
462
305
  if not gen.supports_device(device):
463
306
  logger.info("generator %s is not supported for device %s", gen, device.hostname)
@@ -473,7 +316,7 @@ def _run_entire_generator(gen: "Entire", device: "Device", storage: Storage) ->
473
316
  raise RuntimeError("entire generator should return non-empty path")
474
317
 
475
318
  logger.info("Generating ENTIRE ...")
476
- with GeneratorPerfMesurer(gen, storage, trace_min_duration="0.5") as pm:
319
+ with GeneratorPerfMesurer(gen, trace_min_duration="0.5") as pm:
477
320
  output = gen(device)
478
321
 
479
322
  return GeneratorEntireResult(
@@ -495,7 +338,6 @@ def _make_generator_ctx(gen):
495
338
  def _run_json_fragment_generator(
496
339
  gen: "JSONFragment",
497
340
  device: "Device",
498
- storage: Storage,
499
341
  ) -> Optional[GeneratorResult]:
500
342
  logger = get_logger(generator=_make_generator_ctx(gen))
501
343
  if not gen.supports_device(device):
@@ -520,7 +362,7 @@ def _run_json_fragment_generator(
520
362
  acl_safe = [safe_acl_item_or_list_of_items]
521
363
 
522
364
  logger.info("Generating JSON_FRAGMENT ...")
523
- with GeneratorPerfMesurer(gen, storage) as pm:
365
+ with GeneratorPerfMesurer(gen) as pm:
524
366
  config = gen(device)
525
367
  reload_cmds = gen.get_reload_cmds(device)
526
368
  return GeneratorJSONFragmentResult(
@@ -559,7 +401,7 @@ def _get_generators(module_paths: Union[List[str], dict], storage, device=None):
559
401
  return res_generators
560
402
 
561
403
 
562
- def _get_ref_generators(module_paths: List[str], storage):
404
+ def _get_ref_generators(module_paths: List[str], storage, device):
563
405
  if isinstance(module_paths, dict):
564
406
  module_paths = module_paths.get("default")
565
407
  res_generators = []
@@ -570,302 +412,6 @@ def _get_ref_generators(module_paths: List[str], storage):
570
412
  return res_generators
571
413
 
572
414
 
573
- class InvalidValueFromGenerator(ValueError):
574
- pass
575
-
576
-
577
- class GenStringable(abc.ABC):
578
- @abc.abstractmethod
579
- def gen_str(self) -> str:
580
- pass
581
-
582
-
583
- ParamsList = tabparser.JuniperList
584
-
585
-
586
- def _filter_str(value: Union[str, int, float, tabparser.JuniperList, ParamsList, GenStringable]):
587
- if isinstance(value, (
588
- str,
589
- int,
590
- float,
591
- tabparser.JuniperList,
592
- ParamsList,
593
- )):
594
- return str(value)
595
-
596
- if hasattr(value, "gen_str") and callable(value.gen_str):
597
- return value.gen_str()
598
-
599
- raise InvalidValueFromGenerator("Invalid yield type: %s(%s)" % (type(value).__name__, value))
600
-
601
-
602
- # =====
603
- class BaseGenerator:
604
- TYPE: str
605
- TAGS: list[str]
606
-
607
- def supports_device(self, device: Device) -> bool: # pylint: disable=unused-argument
608
- return True
609
-
610
-
611
- class TreeGenerator(BaseGenerator):
612
- def __init__(self, indent=" "):
613
- self._indents = []
614
- self._rows = []
615
- self._block_path = []
616
- self._indent = indent
617
-
618
- @tracing.contextmanager(min_duration="0.1")
619
- @contextlib.contextmanager
620
- def block(self, *tokens, indent=None):
621
- span = tracing_connector.get().get_current_span()
622
- if span:
623
- span.set_attribute("tokens", " ".join(map(str, tokens)))
624
-
625
- indent = self._indent if indent is None else indent
626
- block = " ".join(map(_filter_str, tokens))
627
- self._block_path.append(block)
628
- self._append_text(block)
629
- self._indents.append(indent)
630
- yield
631
- self._indents.pop(-1)
632
- self._block_path.pop(-1)
633
-
634
- @contextlib.contextmanager
635
- def block_if(self, *tokens, condition=DefaultBlockIfCondition):
636
- if condition is DefaultBlockIfCondition:
637
- condition = (None not in tokens and "" not in tokens)
638
- if condition:
639
- with self.block(*tokens):
640
- yield
641
- return
642
- yield
643
-
644
- @contextlib.contextmanager
645
- def multiblock(self, *blocks):
646
- if blocks:
647
- blk = blocks[0]
648
- tokens = blk if isinstance(blk, (list, tuple)) else [blk]
649
- with self.block(*tokens):
650
- with self.multiblock(*blocks[1:]):
651
- yield
652
- return
653
- yield
654
-
655
- @contextlib.contextmanager
656
- def multiblock_if(self, *blocks, condition=DefaultBlockIfCondition):
657
- if condition is DefaultBlockIfCondition:
658
- condition = (None not in blocks)
659
- if condition:
660
- if blocks:
661
- blk = blocks[0]
662
- tokens = blk if isinstance(blk, (list, tuple)) else [blk]
663
- with self.block(*tokens):
664
- with self.multiblock(*blocks[1:]):
665
- yield
666
- return
667
- yield
668
-
669
- # ===
670
- def _append_text(self, text):
671
- self._append_text_cb(text)
672
-
673
- def _append_text_cb(self, text, row_cb=None):
674
- for row in _split_and_strip(text):
675
- if row_cb:
676
- row = row_cb(row)
677
- self._rows.append("".join(self._indents) + row)
678
-
679
-
680
- class TextGenerator(TreeGenerator):
681
- def __add__(self, line):
682
- self._append_text(line)
683
- return self
684
-
685
- def __iter__(self):
686
- yield from self._rows
687
-
688
-
689
- class PartialGenerator(TreeGenerator):
690
- TYPE = "PARTIAL"
691
- TAGS: List[str] = []
692
-
693
- def __init__(self, storage):
694
- super().__init__()
695
- self.storage = storage
696
- self._annotate = False
697
- self._running_gen = None
698
- self._annotations = []
699
- self._annotation_module = self.__class__.__module__ or ""
700
-
701
- def supports_device(self, device: Device) -> bool:
702
- if self.__class__.run is PartialGenerator.run:
703
- return bool(self._get_vendor_func(device.hw.vendor, "run"))
704
- else:
705
- return True
706
-
707
- def acl(self, device):
708
- acl_func = self._get_vendor_func(device.hw.vendor, "acl")
709
- if acl_func:
710
- return acl_func(device)
711
-
712
- def acl_safe(self, device):
713
- acl_func = self._get_vendor_func(device.hw.vendor, "acl_safe")
714
- if acl_func:
715
- return acl_func(device)
716
-
717
- def run(self, device) -> Iterable[Union[str, tuple]]:
718
- run_func = self._get_vendor_func(device.hw.vendor, "run")
719
- if run_func:
720
- return run_func(device)
721
-
722
- def get_user_runner(self, device):
723
- if self.__class__.run is not PartialGenerator.run:
724
- return self.run
725
- return self._get_vendor_func(device.hw.vendor, "run")
726
-
727
- def _get_vendor_func(self, vendor: str, func_name: str):
728
- attr_name = f"{func_name}_{vendor}"
729
- if hasattr(self, attr_name):
730
- return getattr(self, attr_name)
731
- return None
732
-
733
- # =====
734
-
735
- @classmethod
736
- def get_aliases(cls) -> Set[str]:
737
- return {cls.__name__, *cls.TAGS}
738
-
739
- def __call__(self, device, annotate=False):
740
- self._indents = []
741
- self._rows = []
742
- self._running_gen = self.run(device)
743
- self._annotate = annotate
744
-
745
- if annotate and self.__class__.__module__:
746
- self._annotation_module = ".".join(self.__class__.__module__.split(".")[-2:])
747
-
748
- for text in self._running_gen:
749
- if isinstance(text, tuple):
750
- text = " ".join(map(_filter_str, flatten(text)))
751
- else:
752
- text = _filter_str(text)
753
- self._append_text(text)
754
-
755
- for row in self._rows:
756
- assert re.search(r"\bNone\b", row) is None, "Found 'None' in yield result: %s" % (row)
757
- if annotate:
758
- generated_rows = (add_annotation(x, y) for (x, y) in zip(self._rows, self._annotations))
759
- else:
760
- generated_rows = self._rows
761
- return "\n".join(generated_rows) + "\n"
762
-
763
- def _append_text(self, text):
764
- def annotation_cb(row):
765
- annotation = "%s:%d" % self.get_running_line()
766
- self._annotations.append(annotation)
767
- return row
768
-
769
- self._append_text_cb(
770
- text,
771
- annotation_cb if self._annotate else None
772
- )
773
-
774
- def get_running_line(self):
775
- if not self._running_gen or not self._running_gen.gi_frame:
776
- return (repr(self._running_gen), -1)
777
- return self._annotation_module, self._running_gen.gi_frame.f_lineno
778
-
779
- @classmethod
780
- def literal(cls, item):
781
- return '"{}"'.format(item)
782
-
783
- def __repr__(self):
784
- return "<%s>" % self.__class__.__name__
785
-
786
-
787
- class RefGenerator(PartialGenerator):
788
- def __init__(self, storage, groups=None):
789
- super().__init__(storage)
790
- self.groups = groups
791
-
792
- def ref(self, device):
793
- if hasattr(self, "ref_" + device.hw.vendor):
794
- return getattr(self, "ref_" + device.hw.vendor)(device)
795
- return ""
796
-
797
-
798
- class Entire(BaseGenerator):
799
- TYPE = "ENTIRE"
800
- TAGS: List[str] = []
801
- REQUIRED_PACKAGES: FrozenSet[str] = frozenset()
802
-
803
- def __init__(self, storage):
804
- self.storage = storage
805
- # между генераторами для одного и того же path - выбирается тот что больше
806
- if not hasattr(self, "prio"):
807
- self.prio = 100
808
- self.__device = None
809
-
810
- def supports_device(self, device: Device):
811
- return bool(self.path(device))
812
-
813
- def run(self, device) -> Union[None, str, Iterable[Union[str, tuple]]]:
814
- raise NotImplementedError
815
-
816
- def reload(self, device) -> Optional[str]: # pylint: disable=unused-argument
817
- return
818
-
819
- def get_reload_cmds(self, device) -> str:
820
- ret = self.reload(device) or ""
821
- path = self.path(device)
822
- if path and device.hw.PC and device.hw.soft.startswith(("Cumulus", "SwitchDev", "SONiC")):
823
- parts = []
824
- if ret:
825
- parts.append(ret)
826
- parts.append("/usr/bin/etckeeper commitreload %s" % path)
827
- return "\n".join(parts)
828
- return ret
829
-
830
- def path(self, device) -> Optional[str]:
831
- raise NotImplementedError("Required PATH for ENTIRE generator")
832
-
833
- # pylint: disable=unused-argument
834
- def is_safe(self, device) -> bool:
835
- """Output gen results when --acl-safe flag is used"""
836
- return False
837
-
838
- def read(self, path) -> str:
839
- return pkgutil.get_data(__name__, path).decode()
840
-
841
- def mako(self, text, **kwargs) -> str:
842
- return mako_render(text, dedent=True, device=self.__device, **kwargs)
843
-
844
- def jinja(self, text, **kwargs) -> str:
845
- return jinja_render(text, dedent=True, device=self.__device, **kwargs)
846
-
847
- # =====
848
-
849
- @classmethod
850
- def get_aliases(cls) -> Set[str]:
851
- return {cls.__name__, *cls.TAGS}
852
-
853
- def __call__(self, device):
854
- self.__device = device
855
- parts = []
856
- run_res = self.run(device)
857
- if isinstance(run_res, str):
858
- run_res = (run_res,)
859
- if run_res is None or not isinstance(run_res, (tuple, types.GeneratorType)):
860
- raise Exception("generator %s returns %s" % (self.__class__.__name__, type(run_res)))
861
- for text in run_res:
862
- if isinstance(text, tuple):
863
- text = " ".join(map(_filter_str, flatten(text)))
864
- assert re.search(r"\bNone\b", text) is None, "Found 'None' in yield result: %s" % text
865
- parts.append(text)
866
- return "\n".join(parts)
867
-
868
-
869
415
  def select_generators(gens: GenSelectOptions, classes: Iterable[BaseGenerator]):
870
416
  def contains(obj, where):
871
417
  if where:
@@ -887,135 +433,3 @@ def select_generators(gens: GenSelectOptions, classes: Iterable[BaseGenerator]):
887
433
  flts.append(lambda c: not contains(c, gens.excluded_gens))
888
434
 
889
435
  return filter(lambda x: all(f(x) for f in flts), classes)
890
-
891
-
892
- def _split_and_strip(text):
893
- if "\n" in text:
894
- rows = textwrap.dedent(text).strip().split("\n")
895
- else:
896
- rows = [text]
897
- return rows
898
-
899
-
900
- def _combine_acl_text(
901
- partial_results: Dict[str, GeneratorPartialResult],
902
- acl_getter: Callable[[GeneratorPartialResult], str]
903
- ) -> str:
904
- acl_text = ""
905
- for gr in partial_results.values():
906
- for line in textwrap.dedent(acl_getter(gr)).split("\n"):
907
- if line and not line.isspace():
908
- acl_text += line.rstrip()
909
- acl_text += fr" %generator_names={gr.name}"
910
- acl_text += "\n"
911
- return acl_text
912
-
913
-
914
- class JSONFragment(TreeGenerator):
915
- """Generates parts of JSON config file."""
916
-
917
- TYPE = "JSON_FRAGMENT"
918
- TAGS: List[str] = []
919
-
920
- def __init__(self, storage: Storage):
921
- super().__init__()
922
- self.storage = storage
923
- self._json_config: Dict[str, Any] = {}
924
- self._config_pointer: List[str] = []
925
-
926
- # if two generators edit same file, commands from generator with greater `reload_prio` will be used
927
- if not hasattr(self, "reload_prio"):
928
- self.reload_prio = 100
929
-
930
- def supports_device(self, device: Device):
931
- return bool(self.path(device))
932
-
933
- def path(self, device: Device) -> Optional[str]:
934
- raise NotImplementedError("Required PATH for JSON_FRAGMENT generator")
935
-
936
- @classmethod
937
- def get_aliases(cls) -> Set[str]:
938
- return {cls.__name__, *cls.TAGS}
939
-
940
- def acl(self, device: Device) -> Union[str, List[str]]:
941
- """
942
- Restrict the generator to a specified ACL using JSON Pointer syntax.
943
-
944
- Expected ACL to be a list of strings, but a single string is also allowed.
945
- """
946
- raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
947
-
948
- def acl_safe(self, device: Device) -> Union[str, List[str]]:
949
- """
950
- Restrict the generator to a specified safe ACL using JSON Pointer syntax.
951
-
952
- Expected ACL to be a list of strings, but a single string is also allowed.
953
- """
954
- raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
955
-
956
- def run(self, device: Device):
957
- raise NotImplementedError
958
-
959
- def get_reload_cmds(self, device: Device) -> str:
960
- ret = self.reload(device) or ""
961
- return ret
962
-
963
- def reload(self, device) -> Optional[str]:
964
- raise NotImplementedError
965
-
966
- @contextlib.contextmanager
967
- def block(self, *tokens, indent=None): # pylint: disable=unused-argument
968
- block_str = "".join(map(_filter_str, tokens))
969
- self._config_pointer.append(block_str)
970
- try:
971
- yield
972
- finally:
973
- self._config_pointer.pop()
974
-
975
- @contextlib.contextmanager
976
- def block_piped(self, *tokens, indent=None): # pylint: disable=unused-argument
977
- block_str = "|".join(map(_filter_str, tokens))
978
- self._config_pointer.append(block_str)
979
- try:
980
- yield
981
- finally:
982
- self._config_pointer.pop()
983
-
984
- def __call__(self, device: Device, annotate: bool = False):
985
- for cfg_fragment in self.run(device):
986
- self._set_or_replace_dict(self._config_pointer, cfg_fragment)
987
- return self._json_config
988
-
989
- def _set_or_replace_dict(self, pointer, value):
990
- if not pointer:
991
- if self._json_config == {}:
992
- self._json_config = value
993
- else:
994
- self._json_config = [self._json_config, value]
995
- else:
996
- self._set_dict(self._json_config, pointer, value)
997
-
998
- @classmethod
999
- def _to_str(cls, value: Any) -> str:
1000
- if isinstance(value, str):
1001
- return value
1002
- elif isinstance(value, list):
1003
- return [cls._to_str(x) for x in value]
1004
- elif isinstance(value, dict):
1005
- for k, v in value.items():
1006
- value[k] = cls._to_str(v)
1007
- return value
1008
- return str(value)
1009
-
1010
- @classmethod
1011
- def _set_dict(cls, cfg, pointer, value):
1012
- # pointer has at least one key
1013
- if len(pointer) == 1:
1014
- if pointer[0] in cfg:
1015
- cfg[pointer[0]] = [cfg[pointer[0]], cls._to_str(value)]
1016
- else:
1017
- cfg[pointer[0]] = cls._to_str(value)
1018
- else:
1019
- if pointer[0] not in cfg:
1020
- cfg[pointer[0]] = {}
1021
- cls._set_dict(cfg[pointer[0]], pointer[1:], cls._to_str(value))