synapse 2.176.0__py311-none-any.whl → 2.178.0__py311-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 synapse might be problematic. Click here for more details.
- synapse/axon.py +24 -9
- synapse/cortex.py +337 -172
- synapse/cryotank.py +46 -37
- synapse/datamodel.py +17 -4
- synapse/exc.py +19 -0
- synapse/lib/agenda.py +7 -13
- synapse/lib/aha.py +361 -88
- synapse/lib/auth.py +1520 -0
- synapse/lib/base.py +27 -9
- synapse/lib/cell.py +422 -163
- synapse/lib/config.py +15 -11
- synapse/lib/coro.py +13 -0
- synapse/lib/grammar.py +5 -0
- synapse/lib/hive.py +24 -3
- synapse/lib/hiveauth.py +6 -32
- synapse/lib/layer.py +7 -9
- synapse/lib/link.py +22 -18
- synapse/lib/lmdbslab.py +152 -3
- synapse/lib/modelrev.py +1 -1
- synapse/lib/nexus.py +24 -12
- synapse/lib/schemas.py +136 -0
- synapse/lib/storm.py +61 -29
- synapse/lib/stormlib/aha.py +1 -1
- synapse/lib/stormlib/auth.py +185 -10
- synapse/lib/stormlib/cortex.py +16 -5
- synapse/lib/stormlib/gen.py +80 -0
- synapse/lib/stormlib/imap.py +6 -2
- synapse/lib/stormlib/model.py +55 -0
- synapse/lib/stormlib/modelext.py +60 -0
- synapse/lib/stormlib/smtp.py +12 -2
- synapse/lib/stormlib/tabular.py +212 -0
- synapse/lib/stormtypes.py +14 -1
- synapse/lib/trigger.py +1 -1
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +55 -28
- synapse/models/base.py +7 -0
- synapse/models/biz.py +4 -0
- synapse/models/files.py +8 -1
- synapse/models/inet.py +8 -0
- synapse/telepath.py +32 -17
- synapse/tests/files/aha/certs/cas/synapse.crt +28 -0
- synapse/tests/files/aha/certs/cas/synapse.key +51 -0
- synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.crt +30 -0
- synapse/tests/files/aha/certs/hosts/00.aha.loop.vertex.link.key +51 -0
- synapse/tests/files/aha/certs/users/root@synapse.crt +29 -0
- synapse/tests/files/aha/certs/users/root@synapse.key +51 -0
- synapse/tests/files/changelog/model_2.176.0_16ee721a6b7221344eaf946c3ab4602dda546b1a.yaml.gz +0 -0
- synapse/tests/files/changelog/model_2.176.0_2a25c58bbd344716cd7cbc3f4304d8925b0f4ef2.yaml.gz +0 -0
- synapse/tests/files/rstorm/testsvc.py +1 -1
- synapse/tests/test_axon.py +8 -5
- synapse/tests/test_cortex.py +149 -141
- synapse/tests/test_cryotank.py +4 -4
- synapse/tests/test_datamodel.py +7 -0
- synapse/tests/test_lib_agenda.py +10 -3
- synapse/tests/test_lib_aha.py +336 -490
- synapse/tests/{test_lib_hiveauth.py → test_lib_auth.py} +314 -11
- synapse/tests/test_lib_base.py +20 -0
- synapse/tests/test_lib_cell.py +210 -30
- synapse/tests/test_lib_config.py +4 -3
- synapse/tests/test_lib_httpapi.py +18 -14
- synapse/tests/test_lib_layer.py +33 -33
- synapse/tests/test_lib_link.py +42 -1
- synapse/tests/test_lib_lmdbslab.py +68 -0
- synapse/tests/test_lib_nexus.py +12 -4
- synapse/tests/test_lib_node.py +0 -7
- synapse/tests/test_lib_storm.py +45 -0
- synapse/tests/test_lib_stormlib_aha.py +35 -36
- synapse/tests/test_lib_stormlib_auth.py +21 -0
- synapse/tests/test_lib_stormlib_cell.py +4 -15
- synapse/tests/test_lib_stormlib_cortex.py +12 -12
- synapse/tests/test_lib_stormlib_gen.py +99 -0
- synapse/tests/test_lib_stormlib_imap.py +14 -3
- synapse/tests/test_lib_stormlib_model.py +108 -0
- synapse/tests/test_lib_stormlib_modelext.py +64 -0
- synapse/tests/test_lib_stormlib_smtp.py +51 -0
- synapse/tests/test_lib_stormlib_tabular.py +226 -0
- synapse/tests/test_lib_stormsvc.py +4 -1
- synapse/tests/test_lib_stormtypes.py +10 -0
- synapse/tests/test_model_base.py +3 -0
- synapse/tests/test_model_biz.py +3 -0
- synapse/tests/test_model_files.py +12 -2
- synapse/tests/test_model_inet.py +24 -0
- synapse/tests/test_tools_aha.py +78 -101
- synapse/tests/test_tools_changelog.py +196 -0
- synapse/tests/test_tools_healthcheck.py +4 -3
- synapse/tests/utils.py +87 -121
- synapse/tools/aha/clone.py +50 -0
- synapse/tools/aha/enroll.py +2 -1
- synapse/tools/backup.py +2 -2
- synapse/tools/changelog.py +776 -15
- {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/METADATA +48 -48
- {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/RECORD +95 -82
- {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/WHEEL +1 -1
- {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/LICENSE +0 -0
- {synapse-2.176.0.dist-info → synapse-2.178.0.dist-info}/top_level.txt +0 -0
synapse/tools/changelog.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
import sys
|
|
4
|
+
import copy
|
|
5
|
+
import gzip
|
|
4
6
|
import pprint
|
|
5
7
|
import asyncio
|
|
6
8
|
import argparse
|
|
7
9
|
import datetime
|
|
10
|
+
import tempfile
|
|
8
11
|
import textwrap
|
|
9
12
|
import traceback
|
|
10
13
|
import subprocess
|
|
@@ -12,10 +15,14 @@ import collections
|
|
|
12
15
|
|
|
13
16
|
import regex
|
|
14
17
|
|
|
18
|
+
import synapse.exc as s_exc
|
|
15
19
|
import synapse.common as s_common
|
|
20
|
+
import synapse.cortex as s_cortex
|
|
16
21
|
|
|
17
22
|
import synapse.lib.output as s_output
|
|
23
|
+
import synapse.lib.autodoc as s_autodoc
|
|
18
24
|
import synapse.lib.schemas as s_schemas
|
|
25
|
+
import synapse.lib.version as s_version
|
|
19
26
|
|
|
20
27
|
defstruct = (
|
|
21
28
|
('type', None),
|
|
@@ -25,13 +32,399 @@ defstruct = (
|
|
|
25
32
|
|
|
26
33
|
SKIP_FILES = (
|
|
27
34
|
'.gitkeep',
|
|
35
|
+
'modelrefs',
|
|
28
36
|
)
|
|
29
37
|
|
|
30
38
|
version_regex = r'^v[0-9]\.[0-9]+\.[0-9]+((a|b|rc)[0-9]*)?$'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
|
|
40
|
+
def _getCurrentCommit(outp: s_output.OutPut) -> str | None:
|
|
41
|
+
try:
|
|
42
|
+
ret = subprocess.run(['git', 'rev-parse', 'HEAD'],
|
|
43
|
+
capture_output=True,
|
|
44
|
+
timeout=15,
|
|
45
|
+
check=False,
|
|
46
|
+
text=True,
|
|
47
|
+
)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
outp.printf(f'Error grabbing commit: {e}')
|
|
50
|
+
return
|
|
51
|
+
else:
|
|
52
|
+
commit = ret.stdout.strip()
|
|
53
|
+
assert commit
|
|
54
|
+
return commit
|
|
55
|
+
|
|
56
|
+
async def _getCurrentModl(outp: s_output.OutPut) -> dict:
|
|
57
|
+
with tempfile.TemporaryDirectory() as dirn:
|
|
58
|
+
conf = {'health:sysctl:checks': False}
|
|
59
|
+
async with await s_cortex.Cortex.anit(conf=conf, dirn=dirn) as core:
|
|
60
|
+
modl = await core.getModelDict()
|
|
61
|
+
# Reserialize modl so its consistent with the model on disk
|
|
62
|
+
modl = s_common.yamlloads(s_common.yamldump(modl))
|
|
63
|
+
return modl
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ModelDiffer:
|
|
67
|
+
def __init__(self, current_model: dict, reference_model: dict):
|
|
68
|
+
self.cur_model = current_model
|
|
69
|
+
self.ref_model = reference_model
|
|
70
|
+
self.changes = {}
|
|
71
|
+
|
|
72
|
+
self.cur_iface_to_allifaces = collections.defaultdict(list)
|
|
73
|
+
for iface, info in self.cur_model.get('interfaces').items():
|
|
74
|
+
self.cur_iface_to_allifaces[iface] = [iface]
|
|
75
|
+
q = collections.deque(info.get('interfaces', ()))
|
|
76
|
+
while q:
|
|
77
|
+
_iface = q.popleft()
|
|
78
|
+
if _iface in self.cur_iface_to_allifaces[iface]:
|
|
79
|
+
continue
|
|
80
|
+
self.cur_iface_to_allifaces[iface].append(_iface)
|
|
81
|
+
q.extend(self.cur_model.get('interfaces').get(_iface).get('interfaces', ()))
|
|
82
|
+
|
|
83
|
+
self.cur_type2iface = collections.defaultdict(list)
|
|
84
|
+
|
|
85
|
+
for _type, tnfo in self.cur_model.get('types').items():
|
|
86
|
+
for iface in tnfo.get('info').get('interfaces', ()):
|
|
87
|
+
ifaces = self.cur_iface_to_allifaces[iface]
|
|
88
|
+
for _iface in ifaces:
|
|
89
|
+
if _iface not in self.cur_type2iface[_type]:
|
|
90
|
+
self.cur_type2iface[_type].append(_iface)
|
|
91
|
+
|
|
92
|
+
def _compareEdges(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
93
|
+
changes = {}
|
|
94
|
+
if curv == oldv:
|
|
95
|
+
return changes
|
|
96
|
+
|
|
97
|
+
# Flatten the edges into structures that can be handled
|
|
98
|
+
_curv = {tuple(item[0]): item[1] for item in curv}
|
|
99
|
+
_oldv = {tuple(item[0]): item[1] for item in oldv}
|
|
100
|
+
|
|
101
|
+
curedges = set(_curv.keys())
|
|
102
|
+
oldedges = set(_oldv.keys())
|
|
103
|
+
|
|
104
|
+
new_edges = curedges - oldedges
|
|
105
|
+
del_edges = oldedges - curedges # This should generally not happen...
|
|
106
|
+
assert len(del_edges) == 0, 'A edge was removed from the data model!'
|
|
107
|
+
|
|
108
|
+
if new_edges:
|
|
109
|
+
changes['new_edges'] = {k: _curv.get(k) for k in new_edges}
|
|
110
|
+
|
|
111
|
+
updated_edges = collections.defaultdict(dict)
|
|
112
|
+
for edge, curinfo in _curv.items():
|
|
113
|
+
if edge in new_edges:
|
|
114
|
+
continue
|
|
115
|
+
oldinfo = _oldv.get(edge)
|
|
116
|
+
if curinfo == oldinfo:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# TODO - Support changes to the edges?
|
|
120
|
+
assert False, f'A change was found for the edge: {edge}'
|
|
121
|
+
|
|
122
|
+
if updated_edges:
|
|
123
|
+
changes['updated_edges'] = dict(updated_edges)
|
|
124
|
+
|
|
125
|
+
return changes
|
|
126
|
+
|
|
127
|
+
def _compareForms(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
128
|
+
changes = {}
|
|
129
|
+
if curv == oldv:
|
|
130
|
+
return changes
|
|
131
|
+
|
|
132
|
+
curforms = set(curv.keys())
|
|
133
|
+
oldforms = set(oldv.keys())
|
|
134
|
+
|
|
135
|
+
new_forms = curforms - oldforms
|
|
136
|
+
del_forms = oldforms - curforms # This should generally not happen...
|
|
137
|
+
assert len(del_forms) == 0, 'A form was removed from the data model!'
|
|
138
|
+
|
|
139
|
+
if new_forms:
|
|
140
|
+
changes['new_forms'] = {k: curv.get(k) for k in new_forms}
|
|
141
|
+
|
|
142
|
+
updated_forms = collections.defaultdict(dict)
|
|
143
|
+
for form, curinfo in curv.items():
|
|
144
|
+
if form in new_forms:
|
|
145
|
+
continue
|
|
146
|
+
oldinfo = oldv.get(form)
|
|
147
|
+
if curinfo == oldinfo:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
# Check for different properties
|
|
151
|
+
nprops = curinfo.get('props')
|
|
152
|
+
oprops = oldinfo.get('props')
|
|
153
|
+
|
|
154
|
+
new_props = set(nprops.keys()) - set(oprops.keys())
|
|
155
|
+
del_props = set(oprops.keys()) - set(nprops.keys()) # This should generally not happen...
|
|
156
|
+
assert len(del_props) == 0, 'A form was removed from the data model!'
|
|
157
|
+
|
|
158
|
+
if new_props:
|
|
159
|
+
updated_forms[form]['new_properties'] = {prop: nprops.get(prop) for prop in new_props}
|
|
160
|
+
np_noiface = {}
|
|
161
|
+
ifaces = self.cur_type2iface[form]
|
|
162
|
+
|
|
163
|
+
for prop in new_props:
|
|
164
|
+
# TODO record raw new_props to make bulk edits possible
|
|
165
|
+
is_ifaceprop = False
|
|
166
|
+
for iface in ifaces:
|
|
167
|
+
# Is the prop in the new_interfaces or updated_interfaces lists?
|
|
168
|
+
new_iface = self.changes.get('interfaces').get('new_interfaces', {}).get(iface)
|
|
169
|
+
if new_iface and prop in new_iface.get('props', {}):
|
|
170
|
+
is_ifaceprop = True
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
|
|
174
|
+
if upt_iface and prop in upt_iface.get('new_properties', {}):
|
|
175
|
+
is_ifaceprop = True
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
if is_ifaceprop:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
np_noiface[prop] = nprops.get(prop)
|
|
182
|
+
|
|
183
|
+
if np_noiface:
|
|
184
|
+
updated_forms[form]['new_properties_no_interfaces'] = np_noiface
|
|
185
|
+
|
|
186
|
+
updated_props = {}
|
|
187
|
+
updated_props_noiface = {}
|
|
188
|
+
|
|
189
|
+
deprecated_props = {}
|
|
190
|
+
deprecated_props_noiface = {}
|
|
191
|
+
|
|
192
|
+
for prop, cpinfo in nprops.items():
|
|
193
|
+
if prop in new_props:
|
|
194
|
+
continue
|
|
195
|
+
opinfo = oprops.get(prop)
|
|
196
|
+
if cpinfo == opinfo:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
# Deprecation has a higher priority than updated type information
|
|
200
|
+
if cpinfo.get('deprecated') and not opinfo.get('deprecated'):
|
|
201
|
+
# A deprecated property could be present on an updated iface
|
|
202
|
+
deprecated_props[prop] = cpinfo
|
|
203
|
+
|
|
204
|
+
is_ifaceprop = False
|
|
205
|
+
for iface in self.cur_type2iface[form]:
|
|
206
|
+
upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
|
|
207
|
+
if upt_iface and prop in upt_iface.get('deprecated_properties', {}):
|
|
208
|
+
is_ifaceprop = True
|
|
209
|
+
break
|
|
210
|
+
if is_ifaceprop:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
deprecated_props_noiface[prop] = cpinfo
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
# Check if type change happened, we'll want to document that.
|
|
217
|
+
ctyp = cpinfo.get('type')
|
|
218
|
+
otyp = opinfo.get('type')
|
|
219
|
+
if ctyp == otyp:
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
updated_props[prop] = {'new_type': ctyp, 'old_type': otyp}
|
|
223
|
+
is_ifaceprop = False
|
|
224
|
+
for iface in self.cur_type2iface[form]:
|
|
225
|
+
upt_iface = self.changes.get('interfaces').get('updated_interfaces', {}).get(iface)
|
|
226
|
+
if upt_iface and prop in upt_iface.get('updated_properties', {}):
|
|
227
|
+
is_ifaceprop = True
|
|
228
|
+
break
|
|
229
|
+
if is_ifaceprop:
|
|
230
|
+
continue
|
|
231
|
+
updated_props_noiface[prop] = {'new_type': ctyp, 'old_type': otyp}
|
|
232
|
+
|
|
233
|
+
if updated_props:
|
|
234
|
+
updated_forms[form]['updated_properties'] = updated_props
|
|
235
|
+
|
|
236
|
+
if updated_props_noiface:
|
|
237
|
+
updated_forms[form]['updated_properties_no_interfaces'] = updated_props_noiface
|
|
238
|
+
|
|
239
|
+
if deprecated_props:
|
|
240
|
+
updated_forms[form]['deprecated_properties'] = deprecated_props
|
|
241
|
+
|
|
242
|
+
if deprecated_props_noiface:
|
|
243
|
+
updated_forms[form]['deprecated_properties_no_interfaces'] = deprecated_props_noiface
|
|
244
|
+
|
|
245
|
+
if updated_forms:
|
|
246
|
+
changes['updated_forms'] = dict(updated_forms)
|
|
247
|
+
|
|
248
|
+
return changes
|
|
249
|
+
|
|
250
|
+
def _compareIfaces(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
251
|
+
changes = {}
|
|
252
|
+
if curv == oldv:
|
|
253
|
+
return changes
|
|
254
|
+
|
|
255
|
+
curfaces = set(curv.keys())
|
|
256
|
+
oldfaces = set(oldv.keys())
|
|
257
|
+
|
|
258
|
+
new_faces = curfaces - oldfaces
|
|
259
|
+
del_faces = oldfaces - curfaces # This should generally not happen...
|
|
260
|
+
assert len(del_faces) == 0, 'An interface was removed from the data model!'
|
|
261
|
+
|
|
262
|
+
if new_faces:
|
|
263
|
+
nv = {}
|
|
264
|
+
for iface in new_faces:
|
|
265
|
+
k = copy.deepcopy(curv.get(iface))
|
|
266
|
+
# Rewrite props into a dictionary for easier lookup later
|
|
267
|
+
k['props'] = {item[0]: {'type': item[1], 'props': item[2]} for item in k['props']}
|
|
268
|
+
nv[iface] = k
|
|
269
|
+
|
|
270
|
+
changes['new_interfaces'] = nv
|
|
271
|
+
|
|
272
|
+
updated_interfaces = collections.defaultdict(dict)
|
|
273
|
+
|
|
274
|
+
for iface, curinfo in curv.items():
|
|
275
|
+
if iface in new_faces:
|
|
276
|
+
continue
|
|
277
|
+
oldinfo = oldv.get(iface)
|
|
278
|
+
|
|
279
|
+
# Did the interface inheritance change?
|
|
280
|
+
if curinfo.get('interfaces') != oldinfo.get('interfaces'):
|
|
281
|
+
updated_interfaces[iface] = {'updated_interfaces': {'curv': curinfo.get('interfaces'),
|
|
282
|
+
'oldv': oldinfo.get('interfaces')}}
|
|
283
|
+
# Did the interface have a property definition change?
|
|
284
|
+
nprops = curinfo.get('props')
|
|
285
|
+
oprops = oldinfo.get('props')
|
|
286
|
+
|
|
287
|
+
# Convert props to dictionary
|
|
288
|
+
nprops = {item[0]: {'type': item[1], 'props': item[2]} for item in nprops}
|
|
289
|
+
oprops = {item[0]: {'type': item[1], 'props': item[2]} for item in oprops}
|
|
290
|
+
|
|
291
|
+
new_props = set(nprops.keys()) - set(oprops.keys())
|
|
292
|
+
del_props = set(oprops.keys()) - set(nprops.keys()) # This should generally not happen...
|
|
293
|
+
assert len(del_props) == 0, f'A prop was removed from the iface {iface}'
|
|
294
|
+
|
|
295
|
+
if new_props:
|
|
296
|
+
updated_interfaces[iface]['new_properties'] = {prop: nprops.get(prop) for prop in new_props}
|
|
297
|
+
|
|
298
|
+
updated_props = {}
|
|
299
|
+
deprecated_props = {}
|
|
300
|
+
for prop, cpinfo in nprops.items():
|
|
301
|
+
if prop in new_props:
|
|
302
|
+
continue
|
|
303
|
+
opinfo = oprops.get(prop)
|
|
304
|
+
if cpinfo == opinfo:
|
|
305
|
+
continue
|
|
306
|
+
|
|
307
|
+
if cpinfo.get('props').get('deprecated') and not opinfo.get('props').get('deprecated'):
|
|
308
|
+
deprecated_props[prop] = cpinfo
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
# Check if type change happened, we'll want to document that.
|
|
312
|
+
ctyp = cpinfo.get('type')
|
|
313
|
+
otyp = opinfo.get('type')
|
|
314
|
+
if ctyp == otyp:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
updated_props[prop] = {'new_type': ctyp, 'old_type': otyp}
|
|
318
|
+
if updated_props:
|
|
319
|
+
updated_interfaces[iface]['updated_properties'] = updated_props
|
|
320
|
+
|
|
321
|
+
if deprecated_props:
|
|
322
|
+
updated_interfaces[iface]['deprecated_properties'] = deprecated_props
|
|
323
|
+
|
|
324
|
+
changes['updated_interfaces'] = dict(updated_interfaces)
|
|
325
|
+
|
|
326
|
+
return changes
|
|
327
|
+
|
|
328
|
+
def _compareTagprops(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
329
|
+
changes = {}
|
|
330
|
+
if curv == oldv:
|
|
331
|
+
return changes
|
|
332
|
+
raise NotImplementedError('_compareTagprops')
|
|
333
|
+
|
|
334
|
+
def _compareTypes(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
335
|
+
changes = {}
|
|
336
|
+
if curv == oldv:
|
|
337
|
+
return changes
|
|
338
|
+
|
|
339
|
+
curtypes = set(curv.keys())
|
|
340
|
+
oldtypes = set(oldv.keys())
|
|
341
|
+
|
|
342
|
+
new_types = curtypes - oldtypes
|
|
343
|
+
del_types = oldtypes - curtypes # This should generally not happen...
|
|
344
|
+
assert len(del_types) == 0, 'A type was removed from the data model!'
|
|
345
|
+
|
|
346
|
+
if new_types:
|
|
347
|
+
changes['new_types'] = {k: curv.get(k) for k in new_types}
|
|
348
|
+
|
|
349
|
+
updated_types = collections.defaultdict(dict)
|
|
350
|
+
deprecated_types = collections.defaultdict(dict)
|
|
351
|
+
|
|
352
|
+
for _type, curinfo in curv.items():
|
|
353
|
+
if _type in new_types:
|
|
354
|
+
continue
|
|
355
|
+
oldinfo = oldv.get(_type)
|
|
356
|
+
if curinfo == oldinfo:
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
cnfo = curinfo.get('info')
|
|
360
|
+
onfo = oldinfo.get('info')
|
|
361
|
+
|
|
362
|
+
if cnfo.get('deprecated') and not onfo.get('deprecated'):
|
|
363
|
+
deprecated_types[_type] = curinfo
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
if cnfo.get('interfaces') != onfo.get('interfaces'):
|
|
367
|
+
updated_types[_type]['updated_interfaces'] = {'curv': cnfo.get('interfaces'),
|
|
368
|
+
'oldv': onfo.get('interfaces'),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if curinfo.get('opts') != oldinfo.get('opts'):
|
|
372
|
+
updated_types[_type]['updated_opts'] = {'curv': curinfo.get('opts'),
|
|
373
|
+
'oldv': oldinfo.get('opts'),
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if updated_types:
|
|
377
|
+
changes['updated_types'] = dict(updated_types)
|
|
378
|
+
|
|
379
|
+
if deprecated_types:
|
|
380
|
+
changes['deprecated_types'] = dict(deprecated_types)
|
|
381
|
+
|
|
382
|
+
return changes
|
|
383
|
+
|
|
384
|
+
def _compareUnivs(self, curv, oldv, outp: s_output.OutPut) -> dict:
|
|
385
|
+
changes = {}
|
|
386
|
+
if curv == oldv:
|
|
387
|
+
return changes
|
|
388
|
+
raise NotImplementedError('_compareUnivs')
|
|
389
|
+
|
|
390
|
+
def diffModl(self, outp: s_output.OutPut) -> dict | None:
|
|
391
|
+
if self.changes:
|
|
392
|
+
return self.changes
|
|
393
|
+
|
|
394
|
+
# These are order sensitive due to interface knowledge being required in order
|
|
395
|
+
# to deconflict downstream changes on forms.
|
|
396
|
+
known_keys = {
|
|
397
|
+
'interfaces': self._compareIfaces,
|
|
398
|
+
'types': self._compareTypes,
|
|
399
|
+
'forms': self._compareForms,
|
|
400
|
+
'tagprops': self._compareTagprops,
|
|
401
|
+
'edges': self._compareEdges,
|
|
402
|
+
'univs': self._compareUnivs,
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
all_keys = set(self.cur_model.keys()).union(self.ref_model.keys())
|
|
406
|
+
|
|
407
|
+
for key, func in known_keys.items():
|
|
408
|
+
self.changes[key] = func(self.cur_model.get(key),
|
|
409
|
+
self.ref_model.get(key),
|
|
410
|
+
outp)
|
|
411
|
+
all_keys.remove(key)
|
|
412
|
+
|
|
413
|
+
if all_keys:
|
|
414
|
+
outp.printf(f'ERROR: Unknown model key found: {all_keys}')
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
return self.changes
|
|
418
|
+
|
|
419
|
+
def _getModelFile(fp: str) -> dict | None:
|
|
420
|
+
with s_common.genfile(fp) as fd:
|
|
421
|
+
bytz = fd.read()
|
|
422
|
+
large_bytz = gzip.decompress(bytz)
|
|
423
|
+
ref_modl = s_common.yamlloads(large_bytz)
|
|
424
|
+
return ref_modl
|
|
425
|
+
|
|
426
|
+
async def gen(opts: argparse.Namespace,
|
|
427
|
+
outp: s_output.OutPut):
|
|
35
428
|
|
|
36
429
|
name = opts.name
|
|
37
430
|
if name is None:
|
|
@@ -70,10 +463,271 @@ def gen(opts: argparse.Namespace,
|
|
|
70
463
|
|
|
71
464
|
return 0
|
|
72
465
|
|
|
73
|
-
def
|
|
466
|
+
def _gen_model_rst(version, model_ref, changes, current_model, outp: s_output.OutPut, width=80) -> s_autodoc.RstHelp:
|
|
467
|
+
rst = s_autodoc.RstHelp()
|
|
468
|
+
rst.addHead(f'{version} Model Updates', link=f'.. _{model_ref}:')
|
|
469
|
+
rst.addLines(f'The following model updates were made during the ``{version}`` Synapse release.')
|
|
470
|
+
|
|
471
|
+
if new_interfaces := changes.get('interfaces').get('new_interfaces'):
|
|
472
|
+
rst.addHead('New Interfaces', lvl=1)
|
|
473
|
+
for interface, info in new_interfaces.items():
|
|
474
|
+
rst.addLines(f'``{interface}``')
|
|
475
|
+
rst.addLines(*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
476
|
+
width=width))
|
|
477
|
+
rst.addLines('\n')
|
|
478
|
+
|
|
479
|
+
# Deconflict new_forms vs new_types -> do not add types which appear in new_forms.
|
|
480
|
+
new_forms = changes.get('forms').get('new_forms', {})
|
|
481
|
+
new_types = changes.get('types').get('new_types', {})
|
|
482
|
+
types_to_document = {k: v for k, v in new_types.items() if k not in new_forms}
|
|
483
|
+
|
|
484
|
+
if types_to_document:
|
|
485
|
+
rst.addHead('New Types', lvl=1)
|
|
486
|
+
for _type, info in types_to_document.items():
|
|
487
|
+
rst.addLines(f'``{_type}``')
|
|
488
|
+
rst.addLines(*textwrap.wrap(info.get('info').get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
489
|
+
width=width))
|
|
490
|
+
rst.addLines('\n')
|
|
491
|
+
|
|
492
|
+
if new_forms:
|
|
493
|
+
rst.addHead('New Forms', lvl=1)
|
|
494
|
+
for form, info in new_forms.items():
|
|
495
|
+
rst.addLines(f'``{form}``')
|
|
496
|
+
# Pull the form doc from the current model directly. In the event of an existing
|
|
497
|
+
# type being turned into a form + then reindexed, it would not show up in the
|
|
498
|
+
# type diff, so we can't rely on the doc being present there.
|
|
499
|
+
doc = current_model.get('types').get(form).get('info').get('doc')
|
|
500
|
+
rst.addLines(*textwrap.wrap(doc, initial_indent=' ', subsequent_indent=' ',
|
|
501
|
+
width=width))
|
|
502
|
+
rst.addLines('\n')
|
|
503
|
+
|
|
504
|
+
# Check for new properties
|
|
505
|
+
updated_forms = changes.get('forms').get('updated_forms', {})
|
|
506
|
+
new_props = []
|
|
507
|
+
for form, info in updated_forms.items():
|
|
508
|
+
if 'new_properties' in info:
|
|
509
|
+
new_props.append((form, info))
|
|
510
|
+
if new_props:
|
|
511
|
+
rst.addHead('New Properties', lvl=1)
|
|
512
|
+
new_props.sort(key=lambda x: x[0])
|
|
513
|
+
for form, info in new_props:
|
|
514
|
+
rst.addLines(f'``{form}``')
|
|
515
|
+
new_form_props = list(info.get('new_properties').items())
|
|
516
|
+
if len(new_form_props) > 1:
|
|
517
|
+
rst.addLines(' The form had the following properties added to it:', '\n')
|
|
518
|
+
new_form_props.sort(key=lambda x: x[0])
|
|
519
|
+
for name, info in new_form_props:
|
|
520
|
+
lines = [
|
|
521
|
+
f' ``{name}``',
|
|
522
|
+
*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
523
|
+
width=width),
|
|
524
|
+
'\n'
|
|
525
|
+
]
|
|
526
|
+
rst.addLines(*lines)
|
|
527
|
+
|
|
528
|
+
else:
|
|
529
|
+
name, info = new_form_props[0]
|
|
530
|
+
lines = [
|
|
531
|
+
' The form had the following property added to it:',
|
|
532
|
+
'\n'
|
|
533
|
+
f' ``{name}``',
|
|
534
|
+
*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
535
|
+
width=width),
|
|
536
|
+
'\n'
|
|
537
|
+
]
|
|
538
|
+
rst.addLines(*lines)
|
|
539
|
+
|
|
540
|
+
# Updated interfaces
|
|
541
|
+
if updated_interfaces := changes.get('interfaces').get('updated_interfaces', {}):
|
|
542
|
+
upd_ifaces = list(updated_interfaces.items())
|
|
543
|
+
upd_ifaces.sort(key=lambda x: x[0])
|
|
544
|
+
rst.addHead('Updated Interfaces', lvl=1)
|
|
545
|
+
for iface, info in upd_ifaces:
|
|
546
|
+
lines = [f'``{iface}``',
|
|
547
|
+
]
|
|
548
|
+
for key, valu in sorted(info.items(), key=lambda x: x[0]):
|
|
549
|
+
if key == 'deprecated_properties':
|
|
550
|
+
for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
|
|
551
|
+
mesg = f'The interface property ``{prop}`` has been deprecated.'
|
|
552
|
+
lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
553
|
+
width=width))
|
|
554
|
+
lines.append('\n')
|
|
555
|
+
elif key == 'new_properties':
|
|
556
|
+
for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
|
|
557
|
+
mesg = f'The property ``{prop}`` has been added to the interface.'
|
|
558
|
+
lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
559
|
+
width=width))
|
|
560
|
+
lines.append('\n')
|
|
561
|
+
elif key == 'updated_properties':
|
|
562
|
+
for prop, pnfo in sorted(valu.items(), key=lambda x: x[0]):
|
|
563
|
+
mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
|
|
564
|
+
f' to {pnfo.get("new_type")}.'
|
|
565
|
+
lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
566
|
+
width=width))
|
|
567
|
+
lines.append('\n')
|
|
568
|
+
else: # pragma: no cover
|
|
569
|
+
outp.printf(f'Unknown key: {key=} {valu=}')
|
|
570
|
+
raise s_exc.SynErr(mesg=f'Unknown updated interface key: {key=} {valu=}')
|
|
571
|
+
rst.addLines(*lines)
|
|
572
|
+
|
|
573
|
+
# Updated types
|
|
574
|
+
if updated_types := changes.get('types').get('updated_types', {}):
|
|
575
|
+
upd_types = list(updated_types.items())
|
|
576
|
+
upd_types.sort(key=lambda x: x[0])
|
|
577
|
+
rst.addHead('Updated Types', lvl=1)
|
|
578
|
+
for _type, info in upd_types:
|
|
579
|
+
lines = [f'``{_type}``',
|
|
580
|
+
]
|
|
581
|
+
for key, valu in sorted(info.items(), key=lambda x: x[0]):
|
|
582
|
+
if key == 'updated_interfaces':
|
|
583
|
+
mesg = f'The type interface has been modified from {valu.get("oldv")}' \
|
|
584
|
+
f' to {valu.get("curv")}.'
|
|
585
|
+
lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
586
|
+
width=width))
|
|
587
|
+
lines.append('\n')
|
|
588
|
+
elif key == 'updated_opts':
|
|
589
|
+
mesg = f'The type has been modified from {valu.get("oldv")}' \
|
|
590
|
+
f' to {valu.get("curv")}.'
|
|
591
|
+
lines.extend(textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
592
|
+
width=width))
|
|
593
|
+
lines.append('\n')
|
|
594
|
+
else: # pragma: no cover
|
|
595
|
+
outp.printf(f'Unknown key: {key=} {valu=}')
|
|
596
|
+
raise s_exc.SynErr(mesg=f'Unknown updated type key: {key=} {valu=}')
|
|
597
|
+
rst.addLines(*lines)
|
|
598
|
+
|
|
599
|
+
# Updated Forms
|
|
600
|
+
# We don't really have a "updated forms" to display since the delta for forms data is really property
|
|
601
|
+
# deltas covered elsewhere.
|
|
602
|
+
|
|
603
|
+
# Updated Properties
|
|
604
|
+
upd_props = []
|
|
605
|
+
for form, info in updated_forms.items():
|
|
606
|
+
if 'updated_properties' in info:
|
|
607
|
+
upd_props.append((form, info))
|
|
608
|
+
if upd_props:
|
|
609
|
+
rst.addHead('Updated Properties', lvl=1)
|
|
610
|
+
upd_props.sort(key=lambda x: x[0])
|
|
611
|
+
for form, info in upd_props:
|
|
612
|
+
rst.addLines(f'``{form}``')
|
|
613
|
+
upd_form_props = list(info.get('updated_properties').items())
|
|
614
|
+
if len(upd_form_props) > 1:
|
|
615
|
+
rst.addLines(' The form had the following updated properties:', '\n')
|
|
616
|
+
upd_form_props.sort(key=lambda x: x[0])
|
|
617
|
+
for prop, pnfo in upd_form_props:
|
|
618
|
+
mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
|
|
619
|
+
f' to {pnfo.get("new_type")}.'
|
|
620
|
+
lines = [
|
|
621
|
+
*textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
622
|
+
width=width),
|
|
623
|
+
'\n'
|
|
624
|
+
]
|
|
625
|
+
rst.addLines(*lines)
|
|
626
|
+
|
|
627
|
+
else:
|
|
628
|
+
prop, pnfo = upd_form_props[0]
|
|
629
|
+
mesg = f'The property ``{prop}`` has been modified from {pnfo.get("old_type")}' \
|
|
630
|
+
f' to {pnfo.get("new_type")}.'
|
|
631
|
+
lines = [
|
|
632
|
+
' The form had the following property updated:',
|
|
633
|
+
'\n',
|
|
634
|
+
*textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ',
|
|
635
|
+
width=width),
|
|
636
|
+
'\n'
|
|
637
|
+
]
|
|
638
|
+
rst.addLines(*lines)
|
|
639
|
+
|
|
640
|
+
# Light Edges
|
|
641
|
+
if new_edges := changes.get('edges').get('new_edges'):
|
|
642
|
+
new_edges = list(new_edges.items())
|
|
643
|
+
new_edges.sort(key=lambda x: x[0][1])
|
|
644
|
+
rst.addHead('Light Edges', lvl=1)
|
|
645
|
+
for (n1, name, n2), info in new_edges:
|
|
646
|
+
if n1 is not None and n2 is not None:
|
|
647
|
+
mesg = f'''When used with a ``{n1}`` and an ``{n2}`` node, the edge indicates {info.get('doc')}'''
|
|
648
|
+
elif n1 is None and n2 is not None:
|
|
649
|
+
mesg = f'''When used with a ``{n2}`` target node, the edge indicates {info.get('doc')}'''
|
|
650
|
+
elif n1 is not None and n2 is None:
|
|
651
|
+
mesg = f'''When used with a ``{n1}`` node, the edge indicates {info.get('doc')}'''
|
|
652
|
+
else:
|
|
653
|
+
mesg = info.get('doc')
|
|
654
|
+
|
|
655
|
+
rst.addLines(
|
|
656
|
+
f'``{name}``',
|
|
657
|
+
*textwrap.wrap(mesg, initial_indent=' ', subsequent_indent=' ', width=width),
|
|
658
|
+
'\n',
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Deprecated Interfaces
|
|
662
|
+
# TODO Support deprecated interfaces!
|
|
663
|
+
|
|
664
|
+
# Deprecated Types
|
|
665
|
+
# Deconflict deprecated forms vs deprecated_types, so we do not
|
|
666
|
+
# not call out types which are also forms in the current model.
|
|
667
|
+
deprecated_types = changes.get('types').get('deprecated_types', {})
|
|
668
|
+
deprecated_forms = {k: v for k, v in deprecated_types.items() if k in current_model.get('forms')}
|
|
669
|
+
deprecated_types = {k: v for k, v in deprecated_types.items() if k not in deprecated_forms}
|
|
670
|
+
if deprecated_types:
|
|
671
|
+
rst.addHead('Deprecated Types', lvl=1)
|
|
672
|
+
rst.addLines('The following types have been marked as deprecated:', '\n')
|
|
673
|
+
|
|
674
|
+
for _type, info in deprecated_types.items():
|
|
675
|
+
rst.addLines(
|
|
676
|
+
f'* ``{_type}``',
|
|
677
|
+
)
|
|
678
|
+
rst.addLines('\n')
|
|
679
|
+
|
|
680
|
+
# Deprecated Forms
|
|
681
|
+
if deprecated_forms:
|
|
682
|
+
rst.addHead('Deprecated Types', lvl=1)
|
|
683
|
+
rst.addLines('The following forms have been marked as deprecated:', '\n')
|
|
684
|
+
|
|
685
|
+
for _type, info in deprecated_forms.items():
|
|
686
|
+
rst.addLines(
|
|
687
|
+
f'* ``{_type}``',
|
|
688
|
+
)
|
|
689
|
+
rst.addLines('\n')
|
|
690
|
+
|
|
691
|
+
# Deprecated Properties
|
|
692
|
+
dep_props = []
|
|
693
|
+
for form, info in updated_forms.items():
|
|
694
|
+
if 'deprecated_properties' in info:
|
|
695
|
+
dep_props.append((form, info))
|
|
696
|
+
if dep_props:
|
|
697
|
+
rst.addHead('Deprecated Properties', lvl=1)
|
|
698
|
+
dep_props.sort(key=lambda x: x[0])
|
|
699
|
+
for form, info in dep_props:
|
|
700
|
+
rst.addLines(f'``{form}``')
|
|
701
|
+
dep_form_props = list(info.get('deprecated_properties').items())
|
|
702
|
+
if len(dep_form_props) > 1:
|
|
703
|
+
rst.addLines(' The form had the following properties deprecated:', '\n')
|
|
704
|
+
dep_form_props.sort(key=lambda x: x[0])
|
|
705
|
+
for name, info in dep_form_props:
|
|
706
|
+
lines = [
|
|
707
|
+
f' ``{name}``',
|
|
708
|
+
*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
709
|
+
width=width),
|
|
710
|
+
'\n'
|
|
711
|
+
]
|
|
712
|
+
rst.addLines(*lines)
|
|
713
|
+
|
|
714
|
+
else:
|
|
715
|
+
name, info = dep_form_props[0]
|
|
716
|
+
lines = [
|
|
717
|
+
' The form had the following property deprecated:',
|
|
718
|
+
'\n'
|
|
719
|
+
f' ``{name}``',
|
|
720
|
+
*textwrap.wrap(info.get('doc'), initial_indent=' ', subsequent_indent=' ',
|
|
721
|
+
width=width),
|
|
722
|
+
'\n'
|
|
723
|
+
]
|
|
724
|
+
rst.addLines(*lines)
|
|
725
|
+
|
|
726
|
+
return rst
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
async def format(opts: argparse.Namespace,
|
|
74
730
|
outp: s_output.OutPut):
|
|
75
|
-
if opts.verbose:
|
|
76
|
-
outp.printf(f'{opts=}')
|
|
77
731
|
|
|
78
732
|
if not regex.match(version_regex, opts.version):
|
|
79
733
|
outp.printf(f'Failed to match {opts.version} vs {version_regex}')
|
|
@@ -143,16 +797,15 @@ def format(opts: argparse.Namespace,
|
|
|
143
797
|
outp.printf(f'No files passed validation from {opts.dir}')
|
|
144
798
|
return 1
|
|
145
799
|
|
|
146
|
-
if 'model' in entries:
|
|
147
|
-
outp.printf('Model specific entries are not yet implemented.')
|
|
148
|
-
return 1
|
|
149
|
-
|
|
150
800
|
date = opts.date
|
|
151
801
|
if date is None:
|
|
152
802
|
date = datetime.datetime.utcnow().strftime('%Y-%m-%d')
|
|
153
803
|
header = f'{opts.version} - {date}'
|
|
154
804
|
text = f'{header}\n{"=" * len(header)}\n'
|
|
155
805
|
|
|
806
|
+
modeldiff = False
|
|
807
|
+
clean_vers_ref = opts.version.replace(".", "_")
|
|
808
|
+
model_rst_ref = f'userguide_model_{clean_vers_ref}'
|
|
156
809
|
for key, header in s_schemas._changelogTypes.items():
|
|
157
810
|
dataz = entries.get(key)
|
|
158
811
|
if dataz:
|
|
@@ -167,8 +820,53 @@ def format(opts: argparse.Namespace,
|
|
|
167
820
|
text = f'{text}\n (`#{pr} <https://github.com/vertexproject/synapse/pull/{pr}>`_)'
|
|
168
821
|
if key == 'migration':
|
|
169
822
|
text = text + '\n- See :ref:`datamigration` for more information about automatic migrations.'
|
|
823
|
+
elif key == 'model':
|
|
824
|
+
text = text + f'\n- See :ref:`{model_rst_ref}` for more detailed model changes.'
|
|
825
|
+
modeldiff = True
|
|
170
826
|
text = text + '\n'
|
|
171
827
|
|
|
828
|
+
if modeldiff and opts.model_ref:
|
|
829
|
+
# TODO find previous model file automatically?
|
|
830
|
+
if opts.verbose:
|
|
831
|
+
outp.printf(f'Getting reference model from {opts.model_ref}')
|
|
832
|
+
|
|
833
|
+
ref_modl = _getModelFile(opts.model_ref)
|
|
834
|
+
|
|
835
|
+
if opts.model_current:
|
|
836
|
+
to_modl = _getModelFile(opts.model_current)
|
|
837
|
+
cur_modl = to_modl.get('model')
|
|
838
|
+
if opts.verbose:
|
|
839
|
+
outp.printf(f'Comparing {to_modl.get("version")} - {to_modl.get("commit")} vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
|
|
840
|
+
else:
|
|
841
|
+
cur_modl = await _getCurrentModl(outp)
|
|
842
|
+
if opts.verbose:
|
|
843
|
+
outp.printf(f'Comparing current model vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
|
|
844
|
+
|
|
845
|
+
differ = ModelDiffer(cur_modl, ref_modl.get('model'))
|
|
846
|
+
changes = differ.diffModl(outp)
|
|
847
|
+
has_changes = sum([len(v) for v in changes.values()])
|
|
848
|
+
if has_changes:
|
|
849
|
+
rst = _gen_model_rst(opts.version, model_rst_ref, changes, cur_modl, outp, width=opts.width)
|
|
850
|
+
model_text = rst.getRstText()
|
|
851
|
+
if opts.verbose:
|
|
852
|
+
outp.printf(model_text)
|
|
853
|
+
if opts.model_doc_dir:
|
|
854
|
+
fp = s_common.genpath(opts.model_doc_dir, f'update_{clean_vers_ref}.rst')
|
|
855
|
+
with s_common.genfile(fp) as fd:
|
|
856
|
+
fd.truncate(0)
|
|
857
|
+
fd.write(model_text.encode())
|
|
858
|
+
outp.printf(f'Wrote model changes to {fp}')
|
|
859
|
+
if opts.verbose:
|
|
860
|
+
outp.printf(f'Adding file to git.')
|
|
861
|
+
argv = ['git', 'add', fp]
|
|
862
|
+
ret = subprocess.run(argv, capture_output=True)
|
|
863
|
+
if opts.verbose:
|
|
864
|
+
outp.printf(f'stddout={ret.stdout}')
|
|
865
|
+
outp.printf(f'stderr={ret.stderr}')
|
|
866
|
+
ret.check_returncode()
|
|
867
|
+
else:
|
|
868
|
+
outp.printf(f'No model changes detected.')
|
|
869
|
+
|
|
172
870
|
if opts.rm:
|
|
173
871
|
if opts.verbose:
|
|
174
872
|
outp.printf('Staging file removals in git')
|
|
@@ -180,10 +878,52 @@ def format(opts: argparse.Namespace,
|
|
|
180
878
|
outp.printf(f'stderr={ret.stderr}')
|
|
181
879
|
ret.check_returncode()
|
|
182
880
|
|
|
881
|
+
outp.printf('CHANGELOG ENTRY:\n\n')
|
|
183
882
|
outp.printf(text)
|
|
184
883
|
|
|
185
884
|
return 0
|
|
186
885
|
|
|
886
|
+
async def model(opts: argparse.Namespace,
|
|
887
|
+
outp: s_output.OutPut):
|
|
888
|
+
|
|
889
|
+
if opts.save:
|
|
890
|
+
modl = await _getCurrentModl(outp)
|
|
891
|
+
|
|
892
|
+
dirn = s_common.gendir(opts.cdir, 'modelrefs')
|
|
893
|
+
current_commit = _getCurrentCommit(outp)
|
|
894
|
+
if not current_commit:
|
|
895
|
+
return 1
|
|
896
|
+
wrapped_modl = {
|
|
897
|
+
'model': modl,
|
|
898
|
+
'commit': current_commit,
|
|
899
|
+
'version': s_version.version,
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
fp = s_common.genpath(dirn, f'model_{s_version.verstring}_{current_commit}.yaml.gz')
|
|
903
|
+
with s_common.genfile(fp) as fd:
|
|
904
|
+
fd.truncate(0)
|
|
905
|
+
bytz = s_common.yamldump(wrapped_modl)
|
|
906
|
+
small_bytz = gzip.compress(bytz)
|
|
907
|
+
_ = fd.write(small_bytz)
|
|
908
|
+
|
|
909
|
+
outp.printf(f'Saved model to {fp}')
|
|
910
|
+
return 0
|
|
911
|
+
|
|
912
|
+
if opts.compare:
|
|
913
|
+
ref_modl = _getModelFile(opts.compare)
|
|
914
|
+
if opts.to:
|
|
915
|
+
to_modl = _getModelFile(opts.to)
|
|
916
|
+
modl = to_modl.get('model')
|
|
917
|
+
outp.printf(f'Comparing {to_modl.get("version")} - {to_modl.get("commit")} vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
|
|
918
|
+
else:
|
|
919
|
+
modl = await _getCurrentModl(outp)
|
|
920
|
+
outp.printf(f'Comparing current model vs {ref_modl.get("version")} - {ref_modl.get("commit")}')
|
|
921
|
+
differ = ModelDiffer(modl, ref_modl.get('model'))
|
|
922
|
+
changes = differ.diffModl(outp)
|
|
923
|
+
for line in pprint.pformat(changes).splitlines(keepends=False):
|
|
924
|
+
outp.printf(line)
|
|
925
|
+
return 0
|
|
926
|
+
|
|
187
927
|
async def main(argv, outp=None):
|
|
188
928
|
if outp is None:
|
|
189
929
|
outp = s_output.OutPut()
|
|
@@ -193,10 +933,14 @@ async def main(argv, outp=None):
|
|
|
193
933
|
opts = pars.parse_args(argv)
|
|
194
934
|
if opts.git_dir_check:
|
|
195
935
|
if not os.path.exists(os.path.join(os.getcwd(), '.git')):
|
|
196
|
-
outp.
|
|
936
|
+
outp.printf('Current working directory must be the root of the repository.')
|
|
197
937
|
return 1
|
|
938
|
+
|
|
939
|
+
if opts.verbose:
|
|
940
|
+
outp.printf(f'{opts=}')
|
|
941
|
+
|
|
198
942
|
try:
|
|
199
|
-
return opts.func(opts, outp)
|
|
943
|
+
return await opts.func(opts, outp)
|
|
200
944
|
except Exception as e:
|
|
201
945
|
outp.printf(f'Error running {opts.func}: {traceback.format_exc()}')
|
|
202
946
|
return 1
|
|
@@ -241,8 +985,25 @@ def makeargparser():
|
|
|
241
985
|
help='Date to use with the changelog entry')
|
|
242
986
|
format_pars.add_argument('-r', '--rm', default=False, action='store_true',
|
|
243
987
|
help='Stage the changelog files as deleted files in git.')
|
|
244
|
-
|
|
245
|
-
|
|
988
|
+
format_pars.add_argument('-m', '--model-ref', default=None, action='store', type=str,
|
|
989
|
+
help='Baseline model to use when generating model deltas. This is normally the previous releases model file.')
|
|
990
|
+
format_pars.add_argument('--model-current', default=None, action='store',
|
|
991
|
+
help='Optional model file to use as a reference as the current model.')
|
|
992
|
+
format_pars.add_argument('--model-doc-dir', default=None, action='store',
|
|
993
|
+
help='Directory to write the model changes too.')
|
|
994
|
+
|
|
995
|
+
model_pars = subpars.add_parser('model', help='Helper for working with the Cortex data model.')
|
|
996
|
+
model_pars.set_defaults(func=model)
|
|
997
|
+
mux_model = model_pars.add_mutually_exclusive_group(required=True)
|
|
998
|
+
mux_model.add_argument('-s', '--save', action='store_true', default=False,
|
|
999
|
+
help='Save a copy of the current model to a file.')
|
|
1000
|
+
mux_model.add_argument('-c', '--compare', action='store', default=None,
|
|
1001
|
+
help='Model to compare the current model against. Useful for debugging modl diff functionality.'
|
|
1002
|
+
)
|
|
1003
|
+
model_pars.add_argument('-t', '--to', action='store', default=None,
|
|
1004
|
+
help='The model file to compare against. Will not use current model if specified.')
|
|
1005
|
+
|
|
1006
|
+
for p in (gen_pars, format_pars, model_pars):
|
|
246
1007
|
p.add_argument('-v', '--verbose', default=False, action='store_true',
|
|
247
1008
|
help='Enable verbose output')
|
|
248
1009
|
p.add_argument('--cdir', default='./changes', action='store',
|