annet 0.14.9__tar.gz → 0.14.11__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 (140) hide show
  1. {annet-0.14.9/annet.egg-info → annet-0.14.11}/PKG-INFO +1 -1
  2. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/query.py +3 -0
  3. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/devdb/data/devdb.json +5 -2
  4. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/syntax.py +1 -1
  5. {annet-0.14.9 → annet-0.14.11}/annet/cli.py +64 -4
  6. {annet-0.14.9 → annet-0.14.11}/annet/gen.py +3 -0
  7. {annet-0.14.9 → annet-0.14.11}/annet/implicit.py +8 -0
  8. {annet-0.14.9 → annet-0.14.11}/annet/storage.py +4 -1
  9. {annet-0.14.9 → annet-0.14.11/annet.egg-info}/PKG-INFO +1 -1
  10. {annet-0.14.9 → annet-0.14.11}/AUTHORS +0 -0
  11. {annet-0.14.9 → annet-0.14.11}/LICENSE +0 -0
  12. {annet-0.14.9 → annet-0.14.11}/MANIFEST.in +0 -0
  13. {annet-0.14.9 → annet-0.14.11}/README.md +0 -0
  14. {annet-0.14.9 → annet-0.14.11}/annet/__init__.py +0 -0
  15. {annet-0.14.9 → annet-0.14.11}/annet/adapters/__init__.py +0 -0
  16. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/__init__.py +0 -0
  17. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/__init__.py +0 -0
  18. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/client.py +0 -0
  19. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/manufacturer.py +0 -0
  20. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/models.py +0 -0
  21. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/status_client.py +0 -0
  22. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/common/storage_opts.py +0 -0
  23. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/provider.py +0 -0
  24. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/v24/__init__.py +0 -0
  25. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/v24/storage.py +0 -0
  26. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/v37/__init__.py +0 -0
  27. {annet-0.14.9 → annet-0.14.11}/annet/adapters/netbox/v37/storage.py +0 -0
  28. {annet-0.14.9 → annet-0.14.11}/annet/annet.py +0 -0
  29. {annet-0.14.9 → annet-0.14.11}/annet/annlib/__init__.py +0 -0
  30. {annet-0.14.9 → annet-0.14.11}/annet/annlib/command.py +0 -0
  31. {annet-0.14.9 → annet-0.14.11}/annet/annlib/diff.py +0 -0
  32. {annet-0.14.9 → annet-0.14.11}/annet/annlib/errors.py +0 -0
  33. {annet-0.14.9 → annet-0.14.11}/annet/annlib/filter_acl.py +0 -0
  34. {annet-0.14.9 → annet-0.14.11}/annet/annlib/jsontools.py +0 -0
  35. {annet-0.14.9 → annet-0.14.11}/annet/annlib/lib.py +0 -0
  36. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/__init__.py +0 -0
  37. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/db.py +0 -0
  38. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/devdb/__init__.py +0 -0
  39. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/views/__init__.py +0 -0
  40. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/views/dump.py +0 -0
  41. {annet-0.14.9 → annet-0.14.11}/annet/annlib/netdev/views/hardware.py +0 -0
  42. {annet-0.14.9 → annet-0.14.11}/annet/annlib/output.py +0 -0
  43. {annet-0.14.9 → annet-0.14.11}/annet/annlib/patching.py +0 -0
  44. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/__init__.py +0 -0
  45. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/acl.py +0 -0
  46. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/deploying.py +0 -0
  47. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/ordering.py +0 -0
  48. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rbparser/platform.py +0 -0
  49. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rulebook/__init__.py +0 -0
  50. {annet-0.14.9 → annet-0.14.11}/annet/annlib/rulebook/common.py +0 -0
  51. {annet-0.14.9 → annet-0.14.11}/annet/annlib/tabparser.py +0 -0
  52. {annet-0.14.9 → annet-0.14.11}/annet/annlib/types.py +0 -0
  53. {annet-0.14.9 → annet-0.14.11}/annet/api/__init__.py +0 -0
  54. {annet-0.14.9 → annet-0.14.11}/annet/argparse.py +0 -0
  55. {annet-0.14.9 → annet-0.14.11}/annet/cli_args.py +0 -0
  56. {annet-0.14.9 → annet-0.14.11}/annet/configs/context.yml +0 -0
  57. {annet-0.14.9 → annet-0.14.11}/annet/configs/logging.yaml +0 -0
  58. {annet-0.14.9 → annet-0.14.11}/annet/connectors.py +0 -0
  59. {annet-0.14.9 → annet-0.14.11}/annet/deploy.py +0 -0
  60. {annet-0.14.9 → annet-0.14.11}/annet/diff.py +0 -0
  61. {annet-0.14.9 → annet-0.14.11}/annet/executor.py +0 -0
  62. {annet-0.14.9 → annet-0.14.11}/annet/filtering.py +0 -0
  63. {annet-0.14.9 → annet-0.14.11}/annet/generators/__init__.py +0 -0
  64. {annet-0.14.9 → annet-0.14.11}/annet/generators/base.py +0 -0
  65. {annet-0.14.9 → annet-0.14.11}/annet/generators/common/__init__.py +0 -0
  66. {annet-0.14.9 → annet-0.14.11}/annet/generators/common/initial.py +0 -0
  67. {annet-0.14.9 → annet-0.14.11}/annet/generators/entire.py +0 -0
  68. {annet-0.14.9 → annet-0.14.11}/annet/generators/exceptions.py +0 -0
  69. {annet-0.14.9 → annet-0.14.11}/annet/generators/jsonfragment.py +0 -0
  70. {annet-0.14.9 → annet-0.14.11}/annet/generators/partial.py +0 -0
  71. {annet-0.14.9 → annet-0.14.11}/annet/generators/perf.py +0 -0
  72. {annet-0.14.9 → annet-0.14.11}/annet/generators/ref.py +0 -0
  73. {annet-0.14.9 → annet-0.14.11}/annet/generators/result.py +0 -0
  74. {annet-0.14.9 → annet-0.14.11}/annet/hardware.py +0 -0
  75. {annet-0.14.9 → annet-0.14.11}/annet/lib.py +0 -0
  76. {annet-0.14.9 → annet-0.14.11}/annet/output.py +0 -0
  77. {annet-0.14.9 → annet-0.14.11}/annet/parallel.py +0 -0
  78. {annet-0.14.9 → annet-0.14.11}/annet/patching.py +0 -0
  79. {annet-0.14.9 → annet-0.14.11}/annet/reference.py +0 -0
  80. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/__init__.py +0 -0
  81. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/arista/__init__.py +0 -0
  82. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/arista/iface.py +0 -0
  83. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/aruba/__init__.py +0 -0
  84. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/aruba/ap_env.py +0 -0
  85. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/aruba/misc.py +0 -0
  86. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/cisco/__init__.py +0 -0
  87. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/cisco/iface.py +0 -0
  88. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/cisco/misc.py +0 -0
  89. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/cisco/vlandb.py +0 -0
  90. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/common.py +0 -0
  91. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/deploying.py +0 -0
  92. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/__init__.py +0 -0
  93. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/aaa.py +0 -0
  94. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/bgp.py +0 -0
  95. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/iface.py +0 -0
  96. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/misc.py +0 -0
  97. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/huawei/vlandb.py +0 -0
  98. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/juniper/__init__.py +0 -0
  99. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/nexus/__init__.py +0 -0
  100. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/nexus/iface.py +0 -0
  101. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/patching.py +0 -0
  102. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/ribbon/__init__.py +0 -0
  103. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/arista.deploy +0 -0
  104. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/arista.order +0 -0
  105. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/arista.rul +0 -0
  106. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/aruba.deploy +0 -0
  107. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/aruba.order +0 -0
  108. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/aruba.rul +0 -0
  109. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/cisco.deploy +0 -0
  110. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/cisco.order +0 -0
  111. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/cisco.rul +0 -0
  112. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/huawei.deploy +0 -0
  113. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/huawei.order +0 -0
  114. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/huawei.rul +0 -0
  115. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/juniper.rul +0 -0
  116. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/nexus.deploy +0 -0
  117. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/nexus.order +0 -0
  118. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/nexus.rul +0 -0
  119. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/nokia.rul +0 -0
  120. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/pc.order +0 -0
  121. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/pc.rul +0 -0
  122. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/ribbon.deploy +0 -0
  123. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/ribbon.rul +0 -0
  124. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/routeros.order +0 -0
  125. {annet-0.14.9 → annet-0.14.11}/annet/rulebook/texts/routeros.rul +0 -0
  126. {annet-0.14.9 → annet-0.14.11}/annet/tabparser.py +0 -0
  127. {annet-0.14.9 → annet-0.14.11}/annet/text_term_format.py +0 -0
  128. {annet-0.14.9 → annet-0.14.11}/annet/tracing.py +0 -0
  129. {annet-0.14.9 → annet-0.14.11}/annet/types.py +0 -0
  130. {annet-0.14.9 → annet-0.14.11}/annet.egg-info/SOURCES.txt +0 -0
  131. {annet-0.14.9 → annet-0.14.11}/annet.egg-info/dependency_links.txt +0 -0
  132. {annet-0.14.9 → annet-0.14.11}/annet.egg-info/entry_points.txt +0 -0
  133. {annet-0.14.9 → annet-0.14.11}/annet.egg-info/requires.txt +0 -0
  134. {annet-0.14.9 → annet-0.14.11}/annet.egg-info/top_level.txt +0 -0
  135. {annet-0.14.9 → annet-0.14.11}/annet_generators/__init__.py +0 -0
  136. {annet-0.14.9 → annet-0.14.11}/annet_generators/example/__init__.py +0 -0
  137. {annet-0.14.9 → annet-0.14.11}/annet_generators/example/lldp.py +0 -0
  138. {annet-0.14.9 → annet-0.14.11}/requirements.txt +0 -0
  139. {annet-0.14.9 → annet-0.14.11}/setup.cfg +0 -0
  140. {annet-0.14.9 → annet-0.14.11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.14.9
3
+ Version: 0.14.11
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -21,3 +21,6 @@ class NetboxQuery(Query):
21
21
  def globs(self):
22
22
  # We process every query host as a glob
23
23
  return self.query
24
+
25
+ def is_empty(self) -> bool:
26
+ return len(self.query) == 0
@@ -98,9 +98,9 @@
98
98
  "Nokia.NS7750": " 7750",
99
99
  "Nokia.SR_1s": "SR-1s",
100
100
 
101
- "PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa|[Aa]sterfusion CX)",
101
+ "PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa|[Aa]sterfusion CX|[Uu]fi[Ss]pace)",
102
102
 
103
- "PC.Whitebox": "([Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Aa]sterfusion CX)",
103
+ "PC.Whitebox": "([Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Aa]sterfusion CX|[Uu]fi[Ss]pace)",
104
104
  "PC.Whitebox.Mellanox": "[Mm]ellanox",
105
105
  "PC.Whitebox.Mellanox.SN": " SN",
106
106
  "PC.Whitebox.Mellanox.SN.SN2100": " SN2100",
@@ -117,6 +117,9 @@
117
117
  "PC.Whitebox.Asterfusion.CX": " CX",
118
118
  "PC.Whitebox.Asterfusion.CX.CX500": " CX5\\d\\d",
119
119
  "PC.Whitebox.Asterfusion.CX.CX500.CX532P-N": " CX532P-N",
120
+ "PC.Whitebox.Ufispace": "[Uu]fi[Ss]pace",
121
+ "PC.Whitebox.Ufispace.S": " S",
122
+ "PC.Whitebox.Ufispace.S.S9110-32X": " S9110-32X",
120
123
 
121
124
  "RouterOS": "^([Rr]outer[Oo][Ss]|[Mm]ikro[Tt]ik)",
122
125
 
@@ -31,7 +31,7 @@ def compile_row_regexp(row, flags=0):
31
31
  row = row[:-3]
32
32
  elif "~/" in row:
33
33
  # ~/{regex}/ -> {regex}, () не нужны поскольку уже (?:) - non-captured
34
- row = re.sub(r"~/([^/]+)/", r"\1", row)
34
+ row = re.sub(r"~/(((?!~/).)+)/", r"\1", row)
35
35
  else:
36
36
  row += r"(?:\s|$)"
37
37
  row = re.sub(r"\s+", r"\\s+", row)
@@ -1,25 +1,29 @@
1
1
  import argparse
2
+ import itertools
3
+ import json
2
4
  import operator
3
5
  import os
4
6
  import platform
5
7
  import subprocess
6
8
  import shutil
9
+ import sys
7
10
  from contextlib import ExitStack, contextmanager
8
11
  from typing import Tuple, Iterable
9
12
 
13
+ import tabulate
10
14
  import yaml
11
15
  from contextlog import get_logger
12
16
  from valkit.python import valid_logging_level
13
17
 
14
18
  from annet.deploy import driver_connector, fetcher_connector
15
- from annet import api, cli_args, filtering
19
+ from annet import api, cli_args, filtering, generators
16
20
  from annet.api import collapse_texts, Deployer
17
21
  from annet.argparse import ArgParser, subcommand
18
22
  from annet.diff import gen_sort_diff
19
23
  from annet.gen import Loader, old_raw
20
24
  from annet.lib import get_context_path, repair_context_file
21
25
  from annet.output import output_driver_connector, OutputDriver
22
- from annet.storage import get_storage
26
+ from annet.storage import get_storage, Device
23
27
 
24
28
 
25
29
  def fill_base_args(parser: ArgParser, pkg_name: str, logging_config: str):
@@ -60,14 +64,14 @@ def _gen_current_items(
60
64
 
61
65
 
62
66
  @contextmanager
63
- def get_loader(gen_args: cli_args.GenOptions, args: cli_args.QueryOptions):
67
+ def get_loader(gen_args: cli_args.GenOptions, args: cli_args.QueryOptionsBase):
64
68
  exit_stack = ExitStack()
65
69
  storages = []
66
70
  with exit_stack:
67
71
  connector, connector_opts = get_storage()
68
72
  storage_opts = connector.opts().parse_params(connector_opts, args)
69
73
  storages.append(exit_stack.enter_context(connector.storage()(storage_opts)))
70
- yield Loader(*storages, args=gen_args)
74
+ yield Loader(*storages, args=gen_args, no_empty_warning=args.query.is_empty())
71
75
 
72
76
 
73
77
  @subcommand(is_group=True)
@@ -120,6 +124,62 @@ def show_device_dump(args: cli_args.QueryOptions, arg_out: cli_args.FileOutOptio
120
124
  )
121
125
 
122
126
 
127
+ @subcommand(cli_args.ShowGeneratorsOptions, parent=show)
128
+ def show_generators(args: cli_args.ShowGeneratorsOptions):
129
+ """ List applicable generators (for a device if query is set) """
130
+ arg_gens = cli_args.GenOptions(args)
131
+ with get_loader(arg_gens, args) as loader:
132
+ device: Device | None = None
133
+ devices = loader.devices
134
+ if len(devices) == 1:
135
+ device = devices[0]
136
+ elif len(devices) > 1:
137
+ get_logger().error("cannot show generators for more than one device at once")
138
+ sys.exit(1)
139
+ elif len(devices) == 0 and not args.query.is_empty():
140
+ # the error message will be logged in get_loader()
141
+ sys.exit(1)
142
+
143
+ if not device:
144
+ found_generators = loader.iter_all_gens()
145
+ else:
146
+ found_generators = []
147
+ gens = loader.resolve_gens(loader.devices)
148
+ for g in gens.partial[device]:
149
+ acl_func = g.acl_safe if args.acl_safe else g.acl
150
+ if g.supports_device(device) and acl_func(device):
151
+ found_generators.append(g)
152
+ for g in gens.entire[device]:
153
+ if g.supports_device(device) and g.path(device):
154
+ found_generators.append(g)
155
+ for g in gens.json_fragment[device]:
156
+ if g.supports_device(device) and g.path(device) and g.acl(device):
157
+ found_generators.append(g)
158
+
159
+ output_data = []
160
+ for g in found_generators:
161
+ output_data.append({
162
+ "name": g.__class__.__name__,
163
+ "type": g.TYPE,
164
+ "tags": g.TAGS,
165
+ "module": g.__class__.__module__,
166
+ "description": generators.get_description(g.__class__),
167
+ })
168
+
169
+ if args.format == "json":
170
+ print(json.dumps(output_data))
171
+
172
+ elif args.format == "text":
173
+ keyfunc = operator.itemgetter("type")
174
+ for gen_type, gens in itertools.groupby(sorted(output_data, key=keyfunc, reverse=True), keyfunc):
175
+ print(tabulate.tabulate(
176
+ [(g["name"], ", ".join(g["tags"]), g["module"], g["description"]) for g in gens],
177
+ [f"{gen_type}-Class", "Tags", "Module", "Description"],
178
+ tablefmt="orgtbl",
179
+ ))
180
+ print()
181
+
182
+
123
183
  @subcommand(cli_args.ShowGenOptions)
124
184
  def gen(args: cli_args.ShowGenOptions):
125
185
  """ Generate configuration for devices """
@@ -888,3 +888,6 @@ class Loader:
888
888
 
889
889
  def resolve_gens(self, devices: Iterable[Device]) -> DeviceGenerators:
890
890
  return self._gens.for_devices(devices)
891
+
892
+ def iter_all_gens(self) -> Iterator[BaseGenerator]:
893
+ return self._gens.iter_gens()
@@ -132,6 +132,14 @@ def _implicit_tree(device):
132
132
  !neighbor *
133
133
  no shutdown
134
134
  """
135
+ elif device.hw.Cisco:
136
+ if device.hw.Cisco.Catalyst:
137
+ # this configuration is not visible in running-config when enabled
138
+ text += r"""
139
+ # line console aaa config
140
+ !line con 0
141
+ authorization exec default
142
+ """
135
143
  return parse_text(text)
136
144
 
137
145
 
@@ -75,10 +75,13 @@ class StorageOpts(abc.ABC):
75
75
 
76
76
  class Query(abc.ABC):
77
77
  @classmethod
78
- @abc.abstractclassmethod
78
+ @abc.abstractmethod
79
79
  def new(cls, query: Union[str, Iterable[str]], hosts_range: Optional[slice] = None) -> "Query":
80
80
  pass
81
81
 
82
+ def is_empty(self) -> bool:
83
+ return False
84
+
82
85
 
83
86
  class Device(Protocol):
84
87
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.14.9
3
+ Version: 0.14.11
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
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