mreg-cli 1.2.4__tar.gz → 1.4.0__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.
Files changed (91) hide show
  1. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/.github/workflows/publish.yml +1 -1
  2. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/CHANGELOG.md +54 -0
  3. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/PKG-INFO +3 -2
  4. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/diff.py +110 -22
  5. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/docker-compose.yml +2 -1
  6. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/run_testsuite_and_record_V2.sh +3 -2
  7. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/testsuite +135 -1
  8. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/testsuite-result.json +23434 -12645
  9. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/data/mreg-cli.conf +2 -1
  10. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/_version.py +9 -4
  11. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/abstracts.py +38 -12
  12. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/endpoints.py +8 -0
  13. mreg_cli-1.4.0/mreg_cli/api/errors.py +91 -0
  14. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/models.py +735 -71
  15. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/group.py +3 -1
  16. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/a_aaaa.py +93 -34
  17. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/core.py +77 -33
  18. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/rr.py +4 -4
  19. mreg_cli-1.4.0/mreg_cli/commands/network.py +1260 -0
  20. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/policy.py +8 -3
  21. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/registry.py +3 -1
  22. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/config.py +16 -0
  23. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/help_formatter.py +3 -1
  24. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/main.py +9 -0
  25. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/utilities/api.py +27 -21
  26. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/PKG-INFO +3 -2
  27. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/SOURCES.txt +1 -0
  28. mreg_cli-1.2.4/mreg_cli/commands/network.py +0 -515
  29. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/.github/workflows/test.yml +0 -0
  30. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/.gitignore +0 -0
  31. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/.markdownlint.json +0 -0
  32. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/AUTHORS +0 -0
  33. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/CONTRIBUTING +0 -0
  34. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/LICENSE +0 -0
  35. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/README.md +0 -0
  36. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/Dockerfile +0 -0
  37. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/ci/run_testsuite_and_record.sh +0 -0
  38. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/__about__.py +0 -0
  39. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/__init__.py +0 -0
  40. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/__main__.py +0 -0
  41. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/__init__.py +0 -0
  42. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/fields.py +0 -0
  43. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/api/history.py +0 -0
  44. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/cli.py +0 -0
  45. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/__init__.py +0 -0
  46. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/base.py +0 -0
  47. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/dhcp.py +0 -0
  48. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/help.py +0 -0
  49. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host.py +0 -0
  50. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/__init__.py +0 -0
  51. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/bacnet.py +0 -0
  52. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/host_submodules/cname.py +0 -0
  53. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/label.py +0 -0
  54. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/logging.py +0 -0
  55. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/permission.py +0 -0
  56. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/recording.py +0 -0
  57. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/root.py +0 -0
  58. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/commands/zone.py +0 -0
  59. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/errorbuilder.py +0 -0
  60. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/exceptions.py +0 -0
  61. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/outputmanager.py +0 -0
  62. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/prompt.py +0 -0
  63. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/py.typed +0 -0
  64. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/tags.txt +0 -0
  65. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/tokenfile.py +0 -0
  66. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/types.py +0 -0
  67. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/utilities/__init__.py +0 -0
  68. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/utilities/shared.py +0 -0
  69. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli/utilities/validators.py +0 -0
  70. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/dependency_links.txt +0 -0
  71. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/entry_points.txt +0 -0
  72. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/requires.txt +0 -0
  73. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/mreg_cli.egg-info/top_level.txt +0 -0
  74. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/pyproject.toml +0 -0
  75. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/run.py +0 -0
  76. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/setup.cfg +0 -0
  77. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/__init__.py +0 -0
  78. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/api/__init__.py +0 -0
  79. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/api/test_fields.py +0 -0
  80. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/api/test_models.py +0 -0
  81. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/conftest.py +0 -0
  82. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_errorbuilder.py +0 -0
  83. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_exceptions.py +0 -0
  84. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_outputmanager.py +0 -0
  85. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_prompt.py +0 -0
  86. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_tokenfile.py +0 -0
  87. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/test_types.py +0 -0
  88. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/utilities/__init__.py +0 -0
  89. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tests/utilities/test_api.py +0 -0
  90. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/tox.ini +0 -0
  91. {mreg_cli-1.2.4 → mreg_cli-1.4.0}/uv.lock +0 -0
@@ -125,7 +125,7 @@ jobs:
125
125
  path: dist
126
126
 
127
127
  - name: Push build artifacts to PyPI
128
- uses: pypa/gh-action-pypi-publish@v1.8.14
128
+ uses: pypa/gh-action-pypi-publish@release/v1
129
129
 
130
130
  publish_github:
131
131
  name: Publish GitHub release
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  <!-- ## Unreleased -->
9
9
 
10
+ ## [1.4.0](https://github.com/unioslo/mreg-cli/releases/tag/1.4.0) - 2025-06-20
11
+
12
+ ### Added
13
+
14
+ - Configurable HTTP request timeout. The timeout can be set with the `--timeout` option or with the `timeout` option in the config file.
15
+
16
+ ### Changed
17
+
18
+ - Require `-force` for `host {a,aaaa}_remove` if the argument is a CNAME.
19
+ - Require `-force` when using network or broadcast addresses as arguments for the following commands:
20
+ - `host add`
21
+ - `host a_add`
22
+ - `host aaaa_add`
23
+ - `host a_change`
24
+ - `host aaaa_change`
25
+
26
+ ### Fixed
27
+
28
+ - `host info <ip>` command not working for IP addresses associated with multiple hosts.
29
+
30
+ ## [1.3.0](https://github.com/unioslo/mreg-cli/releases/tag/1.3.0) - 2025-05-20
31
+
32
+ ### Added
33
+
34
+ - Network policy commands:
35
+ - `network policy_add`
36
+ - `network policy_create`
37
+ - `network policy_delete`
38
+ - `network policy_info`
39
+ - `network policy_list`
40
+ - `network policy_rename`
41
+ - `network policy_remove`
42
+ - `network policy_set_description`
43
+ - `network policy_set_prefix`
44
+ - `network policy_unset_prefix`
45
+ - `network policy_attribute_add`
46
+ - `network policy_attribute_create`
47
+ - `network policy_attribute_delete`
48
+ - `network policy_attribute_info`
49
+ - `network policy_attribute_list`
50
+ - `network policy_attribute_remove`
51
+ - `network policy_attribute_set_description`
52
+ - `network community_create`
53
+ - `network community_delete`
54
+ - `network community_info`
55
+ - `network community_list`
56
+ - `network community_rename`
57
+ - `network community_set_description`
58
+ - `network community_host_add`
59
+ - `network community_host_remove`
60
+ - `network create -policy` option for specifying the policy of the network to create.
61
+ - Error handling for DRF errors.
62
+ - Handling of 404 errors that do not contain a JSON payload.
63
+
10
64
  ## [1.2.4](https://github.com/unioslo/mreg-cli/releases/tag/1.2.4) - 2025-01-30
11
65
 
12
66
  ### Fixed
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: mreg-cli
3
- Version: 1.2.4
3
+ Version: 1.4.0
4
4
  Summary: MREG interactive command line client
5
5
  Author: Others (see AUTHORS)
6
6
  Author-email: Øyvind Kolbu <oyvind.kolbu@usit.uio.no>, Safet A <amedov@usit.uio.no>, Magnus Hirth <mhmorgan42@gmail.com>, Nico <nicolaydmohebi@gmail.com>, Marius Bakke <marius.bakke@usit.uio.no>, Fredrik Larsen <fredrik.larsen@usit.uio.no>, Paal Braathen <paal.braathen@usit.uio.no>, Nils Hiorth <nils@nls.me>, Dmytro Karpenko <dmytrok@usit.uio.no>
@@ -701,6 +701,7 @@ Requires-Dist: typing_extensions>=4.12.0
701
701
  Requires-Dist: pydantic[email]>=2.7.1
702
702
  Requires-Dist: pydantic-extra-types>=2.1.0
703
703
  Requires-Dist: platformdirs>=4.3.0
704
+ Dynamic: license-file
704
705
 
705
706
  # MREG CLI [![Build Status](https://github.com/unioslo/mreg-cli/actions/workflows/test.yml/badge.svg)](https://github.com/unioslo/mreg-cli/actions/workflows/test.yml)
706
707
 
@@ -7,13 +7,17 @@ import re
7
7
  import sys
8
8
  import urllib.parse
9
9
  from enum import StrEnum
10
- from typing import Any, Generator, Iterable, NamedTuple
10
+ from typing import Any, Generator, Iterable, Literal, NamedTuple, Self, TypeAlias, overload
11
11
 
12
12
  from rich import box
13
13
  from rich.console import Console, Group
14
14
  from rich.markup import escape
15
15
  from rich.panel import Panel
16
16
  from rich.prompt import Prompt
17
+ from rich.table import Table
18
+
19
+ ProcessedCommand: TypeAlias = dict[str, Any]
20
+ UnprocessedCommand: TypeAlias = dict[str, Any]
17
21
 
18
22
  console = Console(soft_wrap=True, highlight=False)
19
23
  """Stdout console used to print diffs."""
@@ -77,12 +81,35 @@ def fmt_lines(lines: Iterable[str]) -> str:
77
81
  DIFF_HEADER = f"{fmt_line('-expected')}, {fmt_line('+tested')}"
78
82
 
79
83
 
80
- def get_diff(count: int, expected: list[str], result: list[str]) -> Group:
81
- """Print a diff between two lists of strings."""
82
- gen = difflib.ndiff(expected, result)
83
- lines = fmt_lines(gen)
84
- diff_title = f"\n[bold]#{count}[/]"
85
- return Group(diff_title, DIFF_HEADER, Panel(lines, box=box.HORIZONTALS))
84
+ class Diff(NamedTuple):
85
+ """A diff between two lists of strings."""
86
+
87
+ number: int
88
+ """The number of the diff."""
89
+
90
+ diff: list[str]
91
+ """The diff between the two lists of strings."""
92
+
93
+ @property
94
+ def renderable(self) -> Group:
95
+ """Console renderable for the diff."""
96
+ diff_lines = fmt_lines(self.diff)
97
+ diff_title = f"\n[bold]#{self.number}[/]"
98
+ return Group(diff_title, DIFF_HEADER, Panel(diff_lines, box=box.HORIZONTALS))
99
+
100
+ @classmethod
101
+ def from_lines(cls, number: int, expected: list[str], result: list[str]) -> Self:
102
+ """Print a diff between two lists of strings."""
103
+ lines = list(difflib.ndiff(expected, result))
104
+ return cls(number, lines)
105
+
106
+ def get_changed_lines(self) -> Iterable[str]:
107
+ """Get lines with changes (added or removed)."""
108
+ return (line for line in self.diff if line.startswith("+ ") or line.startswith("- "))
109
+
110
+ def get_changes(self) -> str:
111
+ """Get a formatted string of only the changes in the diff."""
112
+ return "\n".join(fmt_line(line) for line in self.get_changed_lines())
86
113
 
87
114
 
88
115
  class Choice(StrEnum):
@@ -142,7 +169,17 @@ def preprocess_json(s: str) -> str:
142
169
  return s
143
170
 
144
171
 
145
- def load_commands(file: str, preprocess: bool = True) -> list[dict[str, Any]]:
172
+ @overload
173
+ def load_commands(file: str, *, preprocess: Literal[False]) -> list[UnprocessedCommand]: ...
174
+
175
+
176
+ @overload
177
+ def load_commands(file: str, *, preprocess: Literal[True]) -> list[ProcessedCommand]: ...
178
+
179
+
180
+ def load_commands(
181
+ file: str, *, preprocess: bool = True
182
+ ) -> list[ProcessedCommand] | list[UnprocessedCommand]:
146
183
  """Load JSON commands from a test suite log file."""
147
184
  with open(file, "r") as f:
148
185
  s = f.read()
@@ -159,8 +196,11 @@ def load_commands(file: str, preprocess: bool = True) -> list[dict[str, Any]]:
159
196
  class TestCommand(NamedTuple):
160
197
  """A single command from a test suite log."""
161
198
 
162
- command: dict[str, Any]
163
- original: dict[str, Any]
199
+ command: ProcessedCommand
200
+ """Processed command data, with placeholders for non-deterministic values."""
201
+
202
+ original: UnprocessedCommand
203
+ """Unprocessed original command data."""
164
204
 
165
205
 
166
206
  class TestSuiteResult:
@@ -168,7 +208,7 @@ class TestSuiteResult:
168
208
 
169
209
  def __init__(self, file: str) -> None:
170
210
  self.file = file
171
- self.commands = load_commands(file)
211
+ self.commands = load_commands(file, preprocess=True)
172
212
  self.commands_original = load_commands(file, preprocess=False)
173
213
 
174
214
  def iterate(
@@ -195,6 +235,23 @@ class TestSuiteResult:
195
235
  )
196
236
 
197
237
 
238
+ class CommandDiff(NamedTuple):
239
+ """(un)resolved diff between two test suite results."""
240
+
241
+ expected: UnprocessedCommand
242
+ """The command that was expected."""
243
+
244
+ result: UnprocessedCommand
245
+ """The command that was actually run."""
246
+
247
+ diff: Diff
248
+ """The diff between the two commands."""
249
+
250
+ def get_command(self) -> str:
251
+ """Get the name of the command that was run."""
252
+ return self.expected.get("command", self.result.get("command", "<unknown>"))
253
+
254
+
198
255
  class CommandDiffer:
199
256
  """Diffs the results (JSON output) of commands from two files."""
200
257
 
@@ -208,8 +265,8 @@ class CommandDiffer:
208
265
  self.expected = TestSuiteResult(file1)
209
266
  self.result = TestSuiteResult(file2)
210
267
 
211
- self.diff_resolved = 0
212
- self.diff_unresolved = 0
268
+ self.diff_resolved: list[CommandDiff] = []
269
+ self.diff_unresolved: list[CommandDiff] = []
213
270
 
214
271
  def diff(self) -> None:
215
272
  """Diff the two files."""
@@ -233,6 +290,14 @@ class CommandDiffer:
233
290
  err_console.print(fmt_line(line))
234
291
  raise CommandCountError(len(expected_commands), len(result_commands))
235
292
 
293
+ def log_unresolved_diff(self, expected: TestCommand, result: TestCommand, diff: Diff) -> None:
294
+ """Log an unresolved diff between two commands."""
295
+ self.diff_unresolved.append(CommandDiff(expected.original, result.original, diff))
296
+
297
+ def log_resolved_diff(self, expected: TestCommand, result: TestCommand, diff: Diff) -> None:
298
+ """Log a resolved diff between two commands."""
299
+ self.diff_resolved.append(CommandDiff(expected.original, result.original, diff))
300
+
236
301
  def diff_command_results(self) -> None:
237
302
  """Diff the contents of each command in the two files."""
238
303
  # NOTE: Only used in review mode
@@ -257,10 +322,10 @@ class CommandDiffer:
257
322
 
258
323
  if expected_lines != result_lines:
259
324
  diff_n += 1
260
- d = get_diff(diff_n, expected_lines, result_lines)
325
+ d = Diff.from_lines(diff_n, expected_lines, result_lines)
261
326
  if not self.review:
262
- self.diff_unresolved += 1
263
- console.print(d)
327
+ self.log_unresolved_diff(expected, result, d)
328
+ console.print(d.renderable)
264
329
  continue # Nothing more to do for this command
265
330
 
266
331
  if no_all:
@@ -268,7 +333,7 @@ class CommandDiffer:
268
333
  elif yes_all:
269
334
  choice = Choice.YES
270
335
  else:
271
- console.print(d)
336
+ console.print(d.renderable)
272
337
  choice = Prompt.ask(
273
338
  f"Accept change #{diff_n}? ({Choice.as_string()})",
274
339
  choices=list(Choice),
@@ -284,23 +349,42 @@ class CommandDiffer:
284
349
  if choice == Choice.YES:
285
350
  # Accept new line
286
351
  new_testsuite_results.append(result.original)
287
- self.diff_resolved += 1
352
+ self.log_resolved_diff(expected, result, d)
288
353
  else:
289
354
  # Keep old line
290
355
  new_testsuite_results.append(expected.original)
291
- self.diff_unresolved += 1
356
+ self.log_unresolved_diff(expected, result, d)
292
357
  else:
293
358
  # No diff, keep old line
294
359
  new_testsuite_results.append(expected.original)
295
360
 
296
361
  # Only write back changes if we are in review mode and there are changes
297
- if self.review and self.diff_resolved > 0:
362
+ if self.review and len(self.diff_resolved) > 0:
298
363
  # Write accepted changes back to file1
299
364
  with open(self.file1, "w") as f:
300
365
  json.dump(new_testsuite_results, f, indent=2)
301
366
  err_console.print(f"Wrote accepted changes back to {self.file1}")
302
367
 
303
368
 
369
+ def print_diff_summary(diffs: list[CommandDiff], title: str) -> None:
370
+ """Print a summary of diffs."""
371
+ if diffs:
372
+ tbl = Table(
373
+ "Command",
374
+ "Diff",
375
+ title=title,
376
+ show_header=True,
377
+ show_lines=True,
378
+ expand=True,
379
+ )
380
+ for cmd in diffs:
381
+ tbl.add_row(
382
+ cmd.get_command(),
383
+ cmd.diff.get_changes(),
384
+ )
385
+ err_console.print(tbl)
386
+
387
+
304
388
  def main() -> None:
305
389
  """Compare two JSON files."""
306
390
  parser = argparse.ArgumentParser(prog="diff.py")
@@ -327,10 +411,14 @@ def main() -> None:
327
411
  # I.e. resolved msg followed by unresolved msg with non-zero exit code
328
412
  resolved = differ.diff_resolved
329
413
  unresolved = differ.diff_unresolved
414
+ print_diff_summary(resolved, "[green italic]Resolved diffs[/]")
415
+ print_diff_summary(unresolved, "[red italic]Unresolved diffs[/]")
330
416
  if resolved:
331
- err_console.print(f"[green]Resolved {resolved} diffs between {file1} and {file2}[/]")
417
+ err_console.print(f"[green]Resolved {len(resolved)} diffs between {file1} and {file2}[/]")
332
418
  if unresolved: # non-zero exit code if unresolved diffs
333
- err_console.print(f"[red]{unresolved} unresolved diffs between {file1} and {file2}.[/]")
419
+ err_console.print(
420
+ f"[red]{len(unresolved)} unresolved diffs between {file1} and {file2}.[/]"
421
+ )
334
422
  sys.exit(1)
335
423
  if not resolved and not unresolved: # no diffs found
336
424
  err_console.print(f"No differences found between {file1} and {file2}")
@@ -13,7 +13,7 @@ services:
13
13
  start_period: 5s
14
14
 
15
15
  mreg:
16
- image: ghcr.io/unioslo/mreg:latest
16
+ image: ghcr.io/unioslo/mreg:master
17
17
  container_name: mreg
18
18
  depends_on:
19
19
  postgres:
@@ -25,6 +25,7 @@ services:
25
25
  - MREG_DB_NAME=mreg
26
26
  - MREG_DB_USER=mreg
27
27
  - MREG_DB_PASSWORD=mreg
28
+ - MREG_MAP_GLOBAL_COMMUNITY_NAMES=1
28
29
  - CI=yes
29
30
  healthcheck:
30
31
  test: sh -c "[ -f /var/run/gunicorn.pid ]"
@@ -28,7 +28,6 @@ function cleanup {
28
28
  docker ps -a | grep "mreg-" | awk '{print $1}' | xargs -r docker stop
29
29
  docker ps -a | grep "mreg-" | awk '{print $1}' | xargs -r docker rm
30
30
  docker images | grep "mreg-" | awk '{print $1}' | xargs -r docker rmi
31
- rm -f new_testsuite_log.json
32
31
  echo "cleanup done."
33
32
  }
34
33
  trap cleanup EXIT
@@ -59,7 +58,7 @@ else
59
58
  fi
60
59
 
61
60
  # give mreg some time to create the database schema and start up
62
- sleep 5s
61
+ sleep 5
63
62
 
64
63
  # create a superuser
65
64
  if docker exec -t mreg uv version >/dev/null; then
@@ -81,8 +80,10 @@ if [[ -n "$GITHUB_ACTIONS" ]]; then
81
80
  docker run --rm --tty --entrypoint bash finished-mreg-tests -c 'cd /build/ci; /root/.local/bin/uv run diff.py testsuite-result.json new_testsuite_log.json'
82
81
  exit $?
83
82
  else
83
+ set +e
84
84
  docker run -it --name finished-mreg-tests --entrypoint bash finished-mreg-tests -c 'cd /build/ci; /root/.local/bin/uv run diff.py testsuite-result.json new_testsuite_log.json --review'
85
85
  EXITCODE=$?
86
+ docker cp finished-mreg-tests:/build/ci/new_testsuite_log.json .
86
87
  docker cp finished-mreg-tests:/build/ci/testsuite-result.json .
87
88
  exit $EXITCODE
88
89
  fi
@@ -68,7 +68,7 @@ host add otherhost -ip 10.0.1.20 -contact support@example.org # fails because r
68
68
  network remove_excluded_range 10.0.1.0/24 10.0.1.20 10.0.1.30
69
69
  host add otherhost -ip 10.0.1.20 -contact support@example.org # should not need force now that net is unfrozen
70
70
  host remove otherhost
71
- network remove 10.0.1.0/24 -f
71
+
72
72
  # IPv6
73
73
  network create -network 2001:db8::/64 -desc "Lorem ipsum dolor sit amet"
74
74
  network set_frozen 2001:db8::/64
@@ -81,6 +81,51 @@ network info 2001:db8::/64
81
81
  host aaaa_add somehost 2001:db8::/64 # should require force, because the network is frozen
82
82
  host aaaa_add somehost 2001:db8::/64 -force
83
83
  host remove somehost -force
84
+ network unset_frozen 2001:db8::/64
85
+
86
+ # Test handling of network and broadcast address arguments
87
+ ## IPv4
88
+ ### Adding/changing A-records for an existing host
89
+ host add somehost -contact support@example.org
90
+ host a_add somehost 10.0.1.0 # should require force because it is a network address
91
+ host a_add somehost 10.0.1.0 -force
92
+ host a_remove somehost 10.0.1.0 # force not required for removal
93
+ host a_add somehost 10.0.1.255 # force again (broadcast address)
94
+ host a_add somehost 10.0.1.255 -force
95
+ host a_change -old 10.0.1.255 -new 10.0.1.0 somehost # changing to network address, requires force
96
+ host a_change -old 10.0.1.255 -new 10.0.1.0 somehost -force
97
+ host remove somehost -force # cleanup
98
+
99
+ ### Adding A-records for a new host
100
+ host add somehost -ip 10.0.1.0 # requires force because it is a network address
101
+ host add somehost -ip 10.0.1.1 -force
102
+ host remove somehost -force -override ipaddress
103
+ host add somehost -ip 10.0.1.255 # requires force because it is a broadcast address
104
+ host add somehost -ip 10.0.1.255 -force
105
+ host a_remove somehost 10.0.1.255 # cleanup
106
+
107
+ ## IPv6
108
+ ### Adding/changing AAAA-records for an existing host
109
+ host aaaa_add somehost 2001:db8:: # should require force because it is a network address
110
+ host aaaa_add somehost 2001:db8:: -force
111
+ host aaaa_remove somehost 2001:db8:: # force not required for removal
112
+ host aaaa_add somehost 2001:db8::ffff:ffff:ffff:ffff # force again (broadcast address)
113
+ host aaaa_add somehost 2001:db8::ffff:ffff:ffff:ffff -force
114
+ host aaaa_change -old 2001:db8::ffff:ffff:ffff:ffff -new 2001:db8:: somehost # changing to network address, requires force
115
+ host aaaa_change -old 2001:db8::ffff:ffff:ffff:ffff -new 2001:db8:: somehost -force
116
+ host aaaa_remove somehost 2001:db8::ffff:ffff:ffff:ffff # force not required for removal
117
+ host remove somehost -force -override ipaddress# cleanup
118
+
119
+ ### Adding AAAA-records for a new host
120
+ host add somehost -ip 10.0.1.0 # requires force because it is a network address
121
+ host add somehost -ip 10.0.1.1 -force
122
+ host remove somehost -force -override ipaddress
123
+ host add somehost -ip 10.0.1.255 # requires force because it is a broadcast address
124
+ host add somehost -ip 10.0.1.255 -force
125
+
126
+ # Cleanup
127
+ host remove somehost -force
128
+ network remove 10.0.1.0/24 -f
84
129
  network remove 2001:db8::/64 -f
85
130
 
86
131
 
@@ -383,6 +428,95 @@ permission network_remove 192.168.0.0/16 mygroup .*
383
428
  label remove postit
384
429
 
385
430
 
431
+ ##### Tests: Network Policy
432
+ network create -network 10.0.2.0/28 -desc "TinyNet"
433
+ network policy_attribute_create foo "Foo attribute"
434
+ network policy_attribute_create bar "Bar attribute"
435
+ network policy_attribute_create baz "Baz attribute"
436
+ network policy_create SecurePolicy "Secure policy for a network" -attribute foo -attribute bar
437
+ network policy_create Autoconfigure "Automatic host configuration" -attribute baz
438
+ network policy_create NoICMP "ICMP disallowed" # no attributes
439
+ network policy_list
440
+ network policy_info SecurePolicy
441
+ network policy_info Autoconfigure
442
+ network policy_add 10.0.2.0/28 SecurePolicy
443
+ network policy_add 10.0.2.0/28 Autoconfigure # requires force
444
+ network policy_add 10.0.2.0/28 Autoconfigure -force
445
+ network info 10.0.2.0/28
446
+ network policy_delete Autoconfigure # requires force
447
+ network policy_delete Autoconfigure -force
448
+ network info 10.0.2.0/28
449
+ network policy_list
450
+ network policy_add 10.0.2.0/28 SecurePolicy
451
+ network policy_rename SecurePolicy InsecurePolicy
452
+ network policy_list
453
+ network info 10.0.2.0/28
454
+ network policy_set_description InsecurePolicy "Insecure policy for a network"
455
+ network policy_attribute_add InsecurePolicy baz
456
+ network policy_attribute_set_description baz "Baz attribute (new)"
457
+ network policy_attribute_list
458
+ network policy_attribute_remove InsecurePolicy foo
459
+ network policy_info InsecurePolicy
460
+ network policy_attribute_delete bar # requires force (in use)
461
+ network policy_attribute_delete bar -force
462
+
463
+ # Communities
464
+ host add tinyhost -ip 10.0.2.0/28 -macaddress 11:22:33:aa:bb:cc -contact tinyhost@example.org
465
+ network community_create 10.0.2.0/28 guests "Guest hosts."
466
+ network community_create 10.0.2.0/28 lab "Lab hosts."
467
+ network community_list 10.0.2.0/28
468
+ network community_host_add tinyhost guests
469
+ host info tinyhost
470
+ network community_host_add tinyhost lab
471
+ host info tinyhost
472
+ # TODO: test network community_host_add with specific IP address on host with multiple IPs
473
+ network community_info 10.0.2.0/28 guests
474
+ network community_info 10.0.2.0/28 lab
475
+
476
+ # Custom communtiy mapping prefix
477
+ network policy_create CustomPrefix "Custom community mapping prefix" -prefix "custom" # no attributes
478
+ network create -network 10.0.3.0/24 -desc "CustomPrefixNet" -policy CustomPrefix # add policy during creation
479
+ network policy_info CustomPrefix
480
+ network community_create 10.0.3.0/24 foo "Foo community"
481
+ network community_info 10.0.3.0/24 foo
482
+ network policy_set_prefix CustomPrefix "newprefix"
483
+ network community_info 10.0.3.0/24 foo
484
+ network policy_unset_prefix CustomPrefix
485
+ network community_info 10.0.3.0/24 foo
486
+ network remove 10.0.3.0/24 -force
487
+ network policy_delete CustomPrefix
488
+
489
+ ## Rename community
490
+ network community_rename 10.0.2.0/28 lab labequipment
491
+ network community_set_description 10.0.2.0/28 labequipment "Lab equipment (expensive)"
492
+ network community_info 10.0.2.0/28 labequipment
493
+
494
+ ## Add another host to a community before displaying communities with hosts
495
+ host add tinierhost -ip 10.0.2.0/28 -contact tinierhost@example.org
496
+ network community_host_add tinierhost guests
497
+ network community_list 10.0.2.0/28 -hosts
498
+
499
+ ## Attempt to add host from other network to community (fails)
500
+ network create -network 10.0.0.0/24 -desc "MediumNet"
501
+ host add mediumhost -ip 10.0.0.0/24 -macaddress 11:22:34:aa:bb:cd -contact mediumhost@example.org
502
+ network community_host_add mediumhost labequipment # fails, community not found in network
503
+ host remove mediumhost -force
504
+ network remove 10.0.0.0/24
505
+
506
+ ## Delete community (remove host from community first)
507
+ network community_delete 10.0.2.0/28 labequipment # fails
508
+ network community_host_remove tinyhost labequipment
509
+ network community_delete 10.0.2.0/28 labequipment
510
+
511
+ ## Delete community with hosts (force)
512
+ network community_delete 10.0.2.0/28 guests # fails
513
+ network community_delete 10.0.2.0/28 guests -force
514
+
515
+ ## Clean up community resources
516
+ host remove tinyhost
517
+ host remove tinierhost
518
+ network remove 10.0.2.0/28 -force
519
+
386
520
  ##### Tests: Zone
387
521
  # - Zone removal
388
522
  zone delete example.org