annet 0.5__tar.gz → 0.6__tar.gz

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.

Files changed (120) hide show
  1. {annet-0.5/annet.egg-info → annet-0.6}/PKG-INFO +1 -1
  2. {annet-0.5 → annet-0.6}/annet/annlib/filter_acl.py +1 -1
  3. {annet-0.5 → annet-0.6}/annet/api/__init__.py +9 -10
  4. {annet-0.5 → annet-0.6}/annet/deploy.py +96 -1
  5. {annet-0.5 → annet-0.6}/annet/gen.py +1 -1
  6. {annet-0.5 → annet-0.6}/annet/rulebook/aruba/ap_env.py +3 -3
  7. {annet-0.5 → annet-0.6}/annet/rulebook/texts/huawei.rul +2 -2
  8. {annet-0.5 → annet-0.6/annet.egg-info}/PKG-INFO +1 -1
  9. {annet-0.5 → annet-0.6}/annet_generators/example/lldp.py +1 -0
  10. {annet-0.5 → annet-0.6}/annet_nbexport/__init__.py +5 -5
  11. {annet-0.5 → annet-0.6}/AUTHORS +0 -0
  12. {annet-0.5 → annet-0.6}/LICENSE +0 -0
  13. {annet-0.5 → annet-0.6}/MANIFEST.in +0 -0
  14. {annet-0.5 → annet-0.6}/README.md +0 -0
  15. {annet-0.5 → annet-0.6}/annet/__init__.py +0 -0
  16. {annet-0.5 → annet-0.6}/annet/annet.py +0 -0
  17. {annet-0.5 → annet-0.6}/annet/annlib/__init__.py +0 -0
  18. {annet-0.5 → annet-0.6}/annet/annlib/command.py +0 -0
  19. {annet-0.5 → annet-0.6}/annet/annlib/diff.py +0 -0
  20. {annet-0.5 → annet-0.6}/annet/annlib/errors.py +0 -0
  21. {annet-0.5 → annet-0.6}/annet/annlib/jsontools.py +0 -0
  22. {annet-0.5 → annet-0.6}/annet/annlib/lib.py +0 -0
  23. {annet-0.5 → annet-0.6}/annet/annlib/netdev/__init__.py +0 -0
  24. {annet-0.5 → annet-0.6}/annet/annlib/netdev/db.py +0 -0
  25. {annet-0.5 → annet-0.6}/annet/annlib/netdev/devdb/__init__.py +0 -0
  26. {annet-0.5 → annet-0.6}/annet/annlib/netdev/devdb/data/devdb.json +0 -0
  27. {annet-0.5 → annet-0.6}/annet/annlib/netdev/views/__init__.py +0 -0
  28. {annet-0.5 → annet-0.6}/annet/annlib/netdev/views/dump.py +0 -0
  29. {annet-0.5 → annet-0.6}/annet/annlib/netdev/views/hardware.py +0 -0
  30. {annet-0.5 → annet-0.6}/annet/annlib/output.py +0 -0
  31. {annet-0.5 → annet-0.6}/annet/annlib/patching.py +0 -0
  32. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/__init__.py +0 -0
  33. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/acl.py +0 -0
  34. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/deploying.py +0 -0
  35. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/ordering.py +0 -0
  36. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/platform.py +0 -0
  37. {annet-0.5 → annet-0.6}/annet/annlib/rbparser/syntax.py +0 -0
  38. {annet-0.5 → annet-0.6}/annet/annlib/rulebook/__init__.py +0 -0
  39. {annet-0.5 → annet-0.6}/annet/annlib/rulebook/common.py +0 -0
  40. {annet-0.5 → annet-0.6}/annet/annlib/tabparser.py +0 -0
  41. {annet-0.5 → annet-0.6}/annet/annlib/types.py +0 -0
  42. {annet-0.5 → annet-0.6}/annet/argparse.py +0 -0
  43. {annet-0.5 → annet-0.6}/annet/cli.py +0 -0
  44. {annet-0.5 → annet-0.6}/annet/cli_args.py +0 -0
  45. {annet-0.5 → annet-0.6}/annet/configs/context.yml +0 -0
  46. {annet-0.5 → annet-0.6}/annet/configs/logging.yaml +0 -0
  47. {annet-0.5 → annet-0.6}/annet/connectors.py +0 -0
  48. {annet-0.5 → annet-0.6}/annet/diff.py +0 -0
  49. {annet-0.5 → annet-0.6}/annet/executor.py +0 -0
  50. {annet-0.5 → annet-0.6}/annet/filtering.py +0 -0
  51. {annet-0.5 → annet-0.6}/annet/generators/__init__.py +0 -0
  52. {annet-0.5 → annet-0.6}/annet/generators/common/__init__.py +0 -0
  53. {annet-0.5 → annet-0.6}/annet/generators/common/initial.py +0 -0
  54. {annet-0.5 → annet-0.6}/annet/hardware.py +0 -0
  55. {annet-0.5 → annet-0.6}/annet/implicit.py +0 -0
  56. {annet-0.5 → annet-0.6}/annet/lib.py +0 -0
  57. {annet-0.5 → annet-0.6}/annet/output.py +0 -0
  58. {annet-0.5 → annet-0.6}/annet/parallel.py +0 -0
  59. {annet-0.5 → annet-0.6}/annet/patching.py +0 -0
  60. {annet-0.5 → annet-0.6}/annet/reference.py +0 -0
  61. {annet-0.5 → annet-0.6}/annet/rulebook/__init__.py +0 -0
  62. {annet-0.5 → annet-0.6}/annet/rulebook/arista/__init__.py +0 -0
  63. {annet-0.5 → annet-0.6}/annet/rulebook/arista/iface.py +0 -0
  64. {annet-0.5 → annet-0.6}/annet/rulebook/aruba/__init__.py +0 -0
  65. {annet-0.5 → annet-0.6}/annet/rulebook/aruba/misc.py +0 -0
  66. {annet-0.5 → annet-0.6}/annet/rulebook/cisco/__init__.py +0 -0
  67. {annet-0.5 → annet-0.6}/annet/rulebook/cisco/iface.py +0 -0
  68. {annet-0.5 → annet-0.6}/annet/rulebook/cisco/misc.py +0 -0
  69. {annet-0.5 → annet-0.6}/annet/rulebook/cisco/vlandb.py +0 -0
  70. {annet-0.5 → annet-0.6}/annet/rulebook/common.py +0 -0
  71. {annet-0.5 → annet-0.6}/annet/rulebook/deploying.py +0 -0
  72. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/__init__.py +0 -0
  73. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/aaa.py +0 -0
  74. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/bgp.py +0 -0
  75. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/iface.py +0 -0
  76. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/misc.py +0 -0
  77. {annet-0.5 → annet-0.6}/annet/rulebook/huawei/vlandb.py +0 -0
  78. {annet-0.5 → annet-0.6}/annet/rulebook/juniper/__init__.py +0 -0
  79. {annet-0.5 → annet-0.6}/annet/rulebook/nexus/__init__.py +0 -0
  80. {annet-0.5 → annet-0.6}/annet/rulebook/nexus/iface.py +0 -0
  81. {annet-0.5 → annet-0.6}/annet/rulebook/patching.py +0 -0
  82. {annet-0.5 → annet-0.6}/annet/rulebook/ribbon/__init__.py +0 -0
  83. {annet-0.5 → annet-0.6}/annet/rulebook/texts/arista.deploy +0 -0
  84. {annet-0.5 → annet-0.6}/annet/rulebook/texts/arista.order +0 -0
  85. {annet-0.5 → annet-0.6}/annet/rulebook/texts/arista.rul +0 -0
  86. {annet-0.5 → annet-0.6}/annet/rulebook/texts/aruba.deploy +0 -0
  87. {annet-0.5 → annet-0.6}/annet/rulebook/texts/aruba.order +0 -0
  88. {annet-0.5 → annet-0.6}/annet/rulebook/texts/aruba.rul +0 -0
  89. {annet-0.5 → annet-0.6}/annet/rulebook/texts/cisco.deploy +0 -0
  90. {annet-0.5 → annet-0.6}/annet/rulebook/texts/cisco.order +0 -0
  91. {annet-0.5 → annet-0.6}/annet/rulebook/texts/cisco.rul +0 -0
  92. {annet-0.5 → annet-0.6}/annet/rulebook/texts/huawei.deploy +0 -0
  93. {annet-0.5 → annet-0.6}/annet/rulebook/texts/huawei.order +0 -0
  94. {annet-0.5 → annet-0.6}/annet/rulebook/texts/juniper.rul +0 -0
  95. {annet-0.5 → annet-0.6}/annet/rulebook/texts/nexus.deploy +0 -0
  96. {annet-0.5 → annet-0.6}/annet/rulebook/texts/nexus.order +0 -0
  97. {annet-0.5 → annet-0.6}/annet/rulebook/texts/nexus.rul +0 -0
  98. {annet-0.5 → annet-0.6}/annet/rulebook/texts/nokia.rul +0 -0
  99. {annet-0.5 → annet-0.6}/annet/rulebook/texts/pc.order +0 -0
  100. {annet-0.5 → annet-0.6}/annet/rulebook/texts/pc.rul +0 -0
  101. {annet-0.5 → annet-0.6}/annet/rulebook/texts/ribbon.deploy +0 -0
  102. {annet-0.5 → annet-0.6}/annet/rulebook/texts/ribbon.rul +0 -0
  103. {annet-0.5 → annet-0.6}/annet/rulebook/texts/routeros.order +0 -0
  104. {annet-0.5 → annet-0.6}/annet/rulebook/texts/routeros.rul +0 -0
  105. {annet-0.5 → annet-0.6}/annet/storage.py +0 -0
  106. {annet-0.5 → annet-0.6}/annet/tabparser.py +0 -0
  107. {annet-0.5 → annet-0.6}/annet/text_term_format.py +0 -0
  108. {annet-0.5 → annet-0.6}/annet/tracing.py +0 -0
  109. {annet-0.5 → annet-0.6}/annet/types.py +0 -0
  110. {annet-0.5 → annet-0.6}/annet.egg-info/SOURCES.txt +0 -0
  111. {annet-0.5 → annet-0.6}/annet.egg-info/dependency_links.txt +0 -0
  112. {annet-0.5 → annet-0.6}/annet.egg-info/entry_points.txt +0 -0
  113. {annet-0.5 → annet-0.6}/annet.egg-info/requires.txt +0 -0
  114. {annet-0.5 → annet-0.6}/annet.egg-info/top_level.txt +0 -0
  115. {annet-0.5 → annet-0.6}/annet_generators/__init__.py +0 -0
  116. {annet-0.5 → annet-0.6}/annet_generators/example/__init__.py +0 -0
  117. {annet-0.5 → annet-0.6}/annet_nbexport/main.py +0 -0
  118. {annet-0.5 → annet-0.6}/requirements.txt +0 -0
  119. {annet-0.5 → annet-0.6}/setup.cfg +0 -0
  120. {annet-0.5 → annet-0.6}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.5
3
+ Version: 0.6
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -116,7 +116,7 @@ def get_op(line: str) -> typing.Tuple[str, str, str]:
116
116
  break
117
117
  if opidx >= 0:
118
118
  op = line[opidx]
119
- indent = line[:opidx] + line[opidx+1:rowstart]
119
+ indent = line[:opidx] + line[opidx + 1:rowstart]
120
120
  if op != " ":
121
121
  indent = indent + " "
122
122
  return op, indent, line[rowstart:]
@@ -331,16 +331,15 @@ def diff(args: cli_args.DiffOptions, loader: ann_gen.Loader, filterer: filtering
331
331
  acl_rules = res.get_acl_rules(args.acl_safe)
332
332
  new_files = res.get_new_files(args.acl_safe)
333
333
  new_json_fragment_files = res.get_new_file_fragments()
334
+ pc_diff_files = []
334
335
  if res.old_files or new_files:
335
- ret[device] = PCDiff(
336
- hostname=device.hostname,
337
- diff_files=list(_pc_diff(device.hostname, res.old_files, new_files)),
338
- )
339
- elif res.old_json_fragment_files or new_json_fragment_files:
340
- ret[device] = PCDiff(
341
- hostname=device.hostname,
342
- diff_files=list(_json_fragment_diff(device.hostname, res.old_json_fragment_files, new_json_fragment_files)),
343
- )
336
+ pc_diff_files.extend(_pc_diff(device.hostname, res.old_files, new_files))
337
+ if res.old_json_fragment_files or new_json_fragment_files:
338
+ pc_diff_files.extend(_json_fragment_diff(device.hostname, res.old_json_fragment_files, new_json_fragment_files))
339
+
340
+ if pc_diff_files:
341
+ pc_diff_files.sort(key=lambda f: f.label)
342
+ ret[device] = PCDiff(hostname=device.hostname, diff_files=pc_diff_files)
344
343
  elif old is not None:
345
344
  rb = rulebook.get_rulebook(device.hw)
346
345
  diff_tree = patching.make_diff(old, new, rb, [acl_rules, res.filter_acl_rules])
@@ -761,7 +760,7 @@ def _json_fragment_diff(
761
760
 
762
761
  def guess_hw(config_text: str):
763
762
  """Пытаемся угадать вендора и hw на основе
764
- текста конфига и annushka/rulebook/texts/*.rul"""
763
+ текста конфига и annet/rulebook/texts/*.rul"""
765
764
  scores = {}
766
765
  hw_provider = hardware_connector.get()
767
766
  for vendor in VENDOR_REVERSES:
@@ -2,17 +2,22 @@
2
2
 
3
3
 
4
4
  import abc
5
+ import itertools
5
6
  import re
6
7
  from collections import namedtuple
7
8
  from contextlib import contextmanager
8
- from typing import Dict, List, Optional, Type
9
+ from typing import Dict, List, Optional, Type, Any, OrderedDict
9
10
 
10
11
  from contextlog import get_logger
11
12
 
12
13
  from annet import text_term_format
14
+ from annet.annlib.command import Command, Question, CommandList
15
+ from annet.annlib.netdev.views.hardware import HardwareView
16
+ from annet.annlib.rbparser.deploying import MakeMessageMatcher, Answer
13
17
  from annet.cli_args import DeployOptions
14
18
  from annet.connectors import Connector
15
19
  from annet.output import TextArgs
20
+ from annet.rulebook import get_rulebook, deploying
16
21
  from annet.storage import Device
17
22
 
18
23
 
@@ -439,3 +444,93 @@ def init_colors():
439
444
  None: curses.color_pair(8),
440
445
  "cyan_blue": curses.color_pair(9),
441
446
  }
447
+
448
+
449
+ class RulebookQuestionHandler:
450
+ def __init__(self, dialogs):
451
+ self._dialogs = dialogs
452
+
453
+ def __call__(self, dev: Connector, cmd: Command, match_content: bytes):
454
+ content = match_content.strip()
455
+ content = content.decode()
456
+ for matcher, answer in self._dialogs.items():
457
+ if matcher(content):
458
+ return Command(answer.text)
459
+
460
+ get_logger().info("no answer in rulebook. dialogs=%s match_content=%s", self._dialogs, match_content)
461
+ return None
462
+
463
+
464
+ def rb_question_to_question(q: MakeMessageMatcher, a: Answer) -> Question: # TODO: drop MakeMessageMatcher
465
+ if not a.send_nl:
466
+ raise Exception("not supported false send_nl")
467
+ text: str = q._text # pylint: disable=protected-access
468
+ is_regexp = False
469
+ if text.startswith("/") and text.endswith("/"):
470
+ is_regexp = True
471
+ text = text[1:-1]
472
+ res = Question(question=text, answer=a.text, is_regexp=is_regexp)
473
+ return res
474
+
475
+
476
+ def make_cmd_params(rule: Dict[str, Any]) -> Dict[str, Any]:
477
+ if rule:
478
+ qa_handler = RulebookQuestionHandler(rule["attrs"]["dialogs"])
479
+ qa_list: List[Question] = []
480
+ for matcher, answer in qa_handler._dialogs.items(): # pylint: disable=protected-access
481
+ qa_list.append(rb_question_to_question(matcher, answer))
482
+ return {
483
+ "questions": qa_list,
484
+ "timeout": rule["attrs"]["timeout"],
485
+ }
486
+ return {
487
+ "timeout": 30,
488
+ }
489
+
490
+
491
+ def make_apply_commands(rule, hw, do_commit, do_finalize):
492
+ apply_logic = rule["attrs"]["apply_logic"]
493
+ before, after = apply_logic(hw, do_commit=do_commit, do_finalize=do_finalize)
494
+ return before, after
495
+
496
+
497
+ def fill_cmd_params(rules: OrderedDict, cmd: Command):
498
+ rule = deploying.match_deploy_rule(rules, (cmd.cmd,), {})
499
+ if rule:
500
+ cmd_params = make_cmd_params(rule)
501
+ cmd.questions = cmd_params.get("questions", None)
502
+ cmd.timeout = cmd_params["timeout"]
503
+
504
+
505
+ def apply_deploy_rulebook(hw: HardwareView, cmd_paths, do_finalize=True, do_commit=True):
506
+ rules = get_rulebook(hw)["deploying"]
507
+ cmds_with_apply = []
508
+ for cmd_path, context in cmd_paths.items():
509
+ rule = deploying.match_deploy_rule(rules, cmd_path, context)
510
+ cmd_params = make_cmd_params(rule)
511
+ before, after = make_apply_commands(rule, hw, do_commit, do_finalize)
512
+
513
+ cmd = Command(cmd_path[-1], **cmd_params)
514
+ # XXX более чистый способ передавать-мета инфу о команде
515
+ cmd.level = len(cmd_path) - 1
516
+ cmds_with_apply.append((cmd, before, after))
517
+
518
+ def _key(item):
519
+ _cmd, before, after = item
520
+ return (tuple(cmd.cmd for cmd in before), tuple(cmd.cmd for cmd in after))
521
+
522
+ cmdlist = CommandList()
523
+ for _k, cmd_before_after in itertools.groupby(cmds_with_apply, key=_key):
524
+ cmd_before_after = list(cmd_before_after)
525
+ _, before, after = cmd_before_after[0]
526
+ for c in before:
527
+ c.level = 0
528
+ fill_cmd_params(rules, c)
529
+ cmdlist.add_cmd(c)
530
+ for cmd, _before, _after in cmd_before_after:
531
+ cmdlist.add_cmd(cmd)
532
+ for c in after:
533
+ c.level = 0
534
+ fill_cmd_params(rules, c)
535
+ cmdlist.add_cmd(c)
536
+ return cmdlist
@@ -693,7 +693,7 @@ def _old_new_get_config_cli(ctx: OldNewDeviceContext, device: Device) -> str:
693
693
  def _old_new_get_config_files(ctx: OldNewDeviceContext, device: Device) -> DeviceDownloadedFiles:
694
694
  old_files = DeviceDownloadedFiles()
695
695
 
696
- for attr in ('failed_files', 'failed_running', 'failed_packages'):
696
+ for attr in ("failed_files", "failed_running", "failed_packages"):
697
697
  if device in getattr(ctx, attr):
698
698
  exc = getattr(ctx, attr).get(device)
699
699
  exc = exc or Exception(f"I can't get device {attr[len('failed_'):]} and I don't know why")
@@ -4,15 +4,15 @@ from annet.annlib.types import Op
4
4
 
5
5
 
6
6
  try:
7
- from annet.executor import CommandList
7
+ from annet.executor import CommandList, Command
8
8
  except ImportError:
9
- from noc.annushka.annet.executor import CommandList
9
+ from noc.annushka.annet.executor import CommandList, Command
10
10
 
11
11
 
12
12
  def apply(hw, do_commit, do_finalize, **_):
13
13
  before, after = CommandList(), CommandList()
14
14
  if do_commit:
15
- after.add_cmd("write memory")
15
+ after.add_cmd(Command("write memory"))
16
16
  return (before, after)
17
17
 
18
18
 
@@ -325,7 +325,7 @@ interface * %logic=huawei.iface.permanent
325
325
  trust upstream
326
326
  %if hw.Huawei.CE:
327
327
  trust *
328
- %else
328
+ %else:
329
329
  trust
330
330
  %endif
331
331
  dhcp snooping check dhcp-rate enable
@@ -442,7 +442,7 @@ snmp-agent trap source
442
442
  snmp-agent packet max-size
443
443
  %if hw.Huawei.Quidway:
444
444
  snmp-agent protocol source-interface %logic=huawei.misc.undo_redo
445
- %else
445
+ %else:
446
446
  snmp-agent protocol source-interface *
447
447
  %endif
448
448
  snmp-agent ~
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.5
3
+ Version: 0.6
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -46,6 +46,7 @@ class Lldp(PartialGenerator):
46
46
  interface all
47
47
  """
48
48
 
49
+
49
50
  def get_generators(store: Storage) -> List[BaseGenerator]:
50
51
  return [
51
52
  Lldp(store),
@@ -4,7 +4,7 @@ import operator
4
4
  import os
5
5
  import os.path
6
6
  import pathlib
7
- from typing import List
7
+ from typing import List, Optional
8
8
 
9
9
  from annet.storage import Device, Storage, Query, StorageOpts, StorageProvider
10
10
  from annet.annlib.netdev.views.hardware import HardwareView
@@ -125,7 +125,7 @@ class NetboxDTO:
125
125
 
126
126
 
127
127
  class AnnetNbExportStorage(Storage):
128
- def __init__(self, opts: "Optional[StorageOpts]" = None):
128
+ def __init__(self, opts: Optional[StorageOpts] = None):
129
129
  self._dump_dir = os.path.join(os.path.dirname(__file__))
130
130
 
131
131
  def __enter__(self):
@@ -146,7 +146,7 @@ class AnnetNbExportStorage(Storage):
146
146
 
147
147
  def make_devices(
148
148
  self,
149
- query: "inventory.Query",
149
+ query,
150
150
  preload_neighbors=False,
151
151
  use_mesh=None,
152
152
  preload_extra_fields=False,
@@ -158,7 +158,7 @@ class AnnetNbExportStorage(Storage):
158
158
  ret.append(DeviceNb(self, NetboxDTO(**device_data), region=file_path.parent.name))
159
159
  return ret
160
160
 
161
- def get_device(self, obj_id, preload_neighbors=False, use_mesh=None, **kwargs) -> "DeviceView":
161
+ def get_device(self, obj_id, preload_neighbors=False, use_mesh=None, **kwargs) -> Device:
162
162
  for file_path, device_data in _read_dump(self._dump_dir):
163
163
  if device_data["name"] == obj_id:
164
164
  return DeviceNb(self, NetboxDTO(**device_data), region=file_path.parent.name)
@@ -168,7 +168,7 @@ class AnnetNbExportStorage(Storage):
168
168
 
169
169
 
170
170
  def _read_dump(dump_dir):
171
- for (dirpath, _dirnames, filenames) in os.walk(dump_dir):
171
+ for (dirpath, _dirnames, filenames) in os.walk(dump_dir):
172
172
  for filename in filenames:
173
173
  if filename != "devices.csv":
174
174
  continue
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes