synapse 2.212.0__py311-none-any.whl → 2.214.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.

Files changed (75) hide show
  1. synapse/cortex.py +37 -6
  2. synapse/daemon.py +6 -6
  3. synapse/exc.py +13 -1
  4. synapse/lib/aha.py +5 -0
  5. synapse/lib/ast.py +2 -6
  6. synapse/lib/boss.py +47 -2
  7. synapse/lib/cell.py +199 -6
  8. synapse/lib/certdir.py +44 -1
  9. synapse/lib/cmd.py +24 -0
  10. synapse/lib/coro.py +8 -2
  11. synapse/lib/drive.py +7 -2
  12. synapse/lib/link.py +11 -3
  13. synapse/lib/schemas.py +1 -1
  14. synapse/lib/scrape.py +3 -1
  15. synapse/lib/snap.py +89 -80
  16. synapse/lib/storm.py +2 -1
  17. synapse/lib/stormlib/imap.py +3 -2
  18. synapse/lib/stormlib/spooled.py +4 -0
  19. synapse/lib/stormtypes.py +18 -0
  20. synapse/lib/task.py +1 -0
  21. synapse/lib/types.py +36 -8
  22. synapse/lib/version.py +2 -2
  23. synapse/models/inet.py +5 -0
  24. synapse/telepath.py +4 -2
  25. synapse/tests/files/testpkg_build_docs/docs/bar.rst +15 -0
  26. synapse/tests/files/testpkg_build_docs/docs/foo.rst +4 -0
  27. synapse/tests/files/testpkg_build_docs/storm/commands/testcmd.storm +0 -0
  28. synapse/tests/files/testpkg_build_docs/storm/modules/apimod.storm +0 -0
  29. synapse/tests/files/testpkg_build_docs/storm/modules/testmod.storm +0 -0
  30. synapse/tests/files/testpkg_build_docs/storm/testcmd.storm +5 -0
  31. synapse/tests/files/testpkg_build_docs/testpkg.yaml +69 -0
  32. synapse/tests/test_cortex.py +20 -1
  33. synapse/tests/test_daemon.py +1 -1
  34. synapse/tests/test_exc.py +6 -0
  35. synapse/tests/test_lib_ast.py +69 -14
  36. synapse/tests/test_lib_boss.py +8 -0
  37. synapse/tests/test_lib_cell.py +119 -8
  38. synapse/tests/test_lib_certdir.py +8 -0
  39. synapse/tests/test_lib_coro.py +5 -0
  40. synapse/tests/test_lib_httpapi.py +10 -2
  41. synapse/tests/test_lib_link.py +1 -1
  42. synapse/tests/test_lib_scrape.py +6 -0
  43. synapse/tests/test_lib_storm.py +123 -1
  44. synapse/tests/test_lib_stormlib_spooled.py +31 -0
  45. synapse/tests/test_lib_stormtypes.py +11 -0
  46. synapse/tests/test_lib_types.py +137 -45
  47. synapse/tests/test_model_crypto.py +8 -0
  48. synapse/tests/test_model_inet.py +7 -0
  49. synapse/tests/test_telepath.py +50 -5
  50. synapse/tests/test_tools_axon.py +304 -0
  51. synapse/tests/test_tools_cortex_layer.py +419 -0
  52. synapse/tests/test_tools_demote.py +114 -0
  53. synapse/tests/test_tools_pkgs_gendocs.py +100 -0
  54. synapse/tests/test_tools_shutdown.py +95 -0
  55. synapse/tests/test_utils.py +22 -1
  56. synapse/tests/utils.py +44 -29
  57. synapse/tools/aha/easycert.py +2 -0
  58. synapse/tools/aha/enroll.py +3 -0
  59. synapse/tools/axon/__init__.py +0 -0
  60. synapse/tools/axon/dump.py +155 -0
  61. synapse/tools/axon/load.py +89 -0
  62. synapse/tools/cortex/__init__.py +0 -0
  63. synapse/tools/cortex/layer/__init__.py +0 -0
  64. synapse/tools/cortex/layer/dump.py +184 -0
  65. synapse/tools/cortex/layer/load.py +129 -0
  66. synapse/tools/demote.py +52 -0
  67. synapse/tools/healthcheck.py +1 -1
  68. synapse/tools/pkgs/gendocs.py +176 -0
  69. synapse/tools/pkgs/pandoc_filter.py +79 -0
  70. synapse/tools/shutdown.py +52 -0
  71. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/METADATA +1 -1
  72. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/RECORD +75 -52
  73. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/WHEEL +0 -0
  74. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/licenses/LICENSE +0 -0
  75. {synapse-2.212.0.dist-info → synapse-2.214.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,95 @@
1
+ import synapse.common as s_common
2
+
3
+ import synapse.lib.cell as s_cell
4
+
5
+
6
+ import synapse.tests.utils as s_test
7
+ import synapse.tools.shutdown as s_t_shutdown
8
+
9
+ class ShutdownToolTest(s_test.SynTest):
10
+
11
+ async def test_tool_shutdown_base(self):
12
+
13
+ async with self.getTestCore() as core:
14
+
15
+ msgs = await core.stormlist('background { $lib.time.sleep(10) }')
16
+ self.stormHasNoWarnErr(msgs)
17
+
18
+ # add a dmon to ensure task.background=True works correctly
19
+ await core.addStormDmon({
20
+ 'iden': s_common.guid(),
21
+ 'storm': 'while (true) { $lib.time.sleep(1) }',
22
+ })
23
+
24
+ argv = ['--url', core.getLocalUrl(), '--timeout', '0']
25
+
26
+ self.eq(1, await s_t_shutdown.main(argv))
27
+
28
+ for task in core.boss.ps():
29
+ if task.name == 'storm':
30
+ await task.kill()
31
+
32
+ self.eq(0, await s_t_shutdown.main(['--url', core.getLocalUrl()]))
33
+
34
+ self.true(await core.waitfini(timeout=1))
35
+
36
+ outp = self.getTestOutp()
37
+ self.eq(1, await s_t_shutdown.main(['--url', 'newp://hehe'], outp=outp))
38
+ outp.expect('Error while attempting graceful shutdown')
39
+
40
+ async def test_tool_shutdown_leader(self):
41
+
42
+ async with self.getTestAha() as aha:
43
+
44
+ with self.getTestDir() as dirn:
45
+
46
+ dirn00 = s_common.genpath(dirn, '00.cell')
47
+ dirn01 = s_common.genpath(dirn, '01.cell')
48
+
49
+ cell00 = await aha.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
50
+ cell01 = await aha.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
51
+ provinfo={'mirror': 'cell'}))
52
+ self.true(cell00.isactive)
53
+ self.false(cell01.isactive)
54
+
55
+ await cell01.sync()
56
+
57
+ # confirm that graceful shutdown with peers also demotes...
58
+ outp = self.getTestOutp()
59
+ argv = ['--url', cell00.getLocalUrl(), '--timeout', '12']
60
+ self.eq(0, await s_t_shutdown.main(argv, outp=outp))
61
+
62
+ self.false(cell00.isactive)
63
+ self.true(cell01.isactive)
64
+ self.true(await cell00.waitfini(timeout=12))
65
+
66
+ # and that graceful shutdown without any cluster peers works too...
67
+ outp.clear()
68
+ argv = ['--url', cell01.getLocalUrl(), '--timeout', '12']
69
+ self.eq(0, await s_t_shutdown.main(argv, outp=outp))
70
+ self.true(await cell01.waitfini(timeout=12))
71
+
72
+ async def test_tool_shutdown_no_features(self):
73
+
74
+ async with self.getTestAha() as aha:
75
+ aha.features.pop('getAhaSvcsByIden')
76
+
77
+ with self.getTestDir() as dirn:
78
+
79
+ dirn00 = s_common.genpath(dirn, '00.cell')
80
+ dirn01 = s_common.genpath(dirn, '01.cell')
81
+
82
+ cell00 = await aha.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
83
+ cell01 = await aha.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
84
+ provinfo={'mirror': 'cell'}))
85
+ self.true(cell00.isactive)
86
+ self.false(cell01.isactive)
87
+
88
+ await cell01.sync()
89
+
90
+ # confirm that graceful shutdown with peers also demotes...
91
+ outp = self.getTestOutp()
92
+ argv = ['--url', cell00.getLocalUrl(), '--timeout', '12']
93
+ with self.getAsyncLoggerStream('synapse.daemon') as stream:
94
+ self.eq(1, await s_t_shutdown.main(argv, outp=outp))
95
+ stream.expect('AHA server does not support feature: getAhaSvcsByIden >= 1')
@@ -3,6 +3,7 @@ import time
3
3
  import logging
4
4
  import unittest
5
5
 
6
+ import synapse.exc as s_exc
6
7
  import synapse.common as s_common
7
8
 
8
9
  import synapse.lib.base as s_base
@@ -100,13 +101,33 @@ class TestUtils(s_t_utils.SynTest):
100
101
  self.skipIfNoPath('newpDoesNotExist', mesg='hehe')
101
102
  self.isin('newpDoesNotExist mesg=hehe', str(cm.exception))
102
103
 
103
- def test_syntest_logstream(self):
104
+ async def test_syntest_logstream(self):
104
105
  with self.getLoggerStream('synapse.tests.test_utils') as stream:
105
106
  logger.error('ruh roh i am a error message')
107
+
108
+ stream.expect('error message')
109
+ with self.raises(s_exc.SynErr):
110
+ stream.expect('does not exist')
111
+
112
+ self.eq(str(stream), 'ruh roh i am a error message\n')
113
+ self.true(repr(stream).endswith('valu: ruh roh i am a error message>'))
114
+ self.true(repr(stream).startswith('<synapse.tests.utils.StreamEvent'))
115
+
106
116
  stream.seek(0)
107
117
  mesgs = stream.read()
108
118
  self.isin('ruh roh', mesgs)
109
119
 
120
+ with self.getAsyncLoggerStream('synapse.tests.test_utils') as stream:
121
+ logger.error('ruh roh i am a new message')
122
+
123
+ stream.expect('new message')
124
+ with self.raises(s_exc.SynErr):
125
+ stream.expect('does not exist')
126
+
127
+ self.eq(str(stream), 'ruh roh i am a new message\n')
128
+ self.true(repr(stream).endswith('valu: ruh roh i am a new message>'))
129
+ self.true(repr(stream).startswith('<synapse.tests.utils.AsyncStreamEvent'))
130
+
110
131
  def test_syntest_logstream_event(self):
111
132
 
112
133
  @s_common.firethread
synapse/tests/utils.py CHANGED
@@ -788,14 +788,9 @@ class CmdGenerator:
788
788
 
789
789
  return retn
790
790
 
791
- class StreamEvent(io.StringIO, threading.Event):
792
- '''
793
- A combination of a io.StringIO object and a threading.Event object.
794
- '''
791
+ class _StreamIOMixin(io.StringIO):
795
792
  def __init__(self, *args, **kwargs):
796
793
  io.StringIO.__init__(self, *args, **kwargs)
797
- threading.Event.__init__(self)
798
- self.mesg = ''
799
794
 
800
795
  def setMesg(self, mesg):
801
796
  '''
@@ -810,6 +805,9 @@ class StreamEvent(io.StringIO, threading.Event):
810
805
  self.mesg = mesg
811
806
  self.clear()
812
807
 
808
+ def __str__(self):
809
+ return self.getvalue()
810
+
813
811
  def write(self, s):
814
812
  io.StringIO.write(self, s)
815
813
  if self.mesg and self.mesg in s:
@@ -819,42 +817,59 @@ class StreamEvent(io.StringIO, threading.Event):
819
817
  '''Get the messages as jsonlines. May throw Json errors if the captured stream is not jsonlines.'''
820
818
  return jsonlines(self.getvalue())
821
819
 
822
- class AsyncStreamEvent(io.StringIO, asyncio.Event):
820
+ def expect(self, substr: str):
821
+ '''
822
+ Check if a string is present in the messages captured by StreamEvent.
823
+
824
+ Args:
825
+ substr (str): String to check for the existence of.
826
+ '''
827
+ valu = self.getvalue()
828
+ if valu.find(substr) == -1:
829
+ mesg = '%s.expect(%s) not in %s' % (self.__class__.__name__, substr, valu)
830
+ raise s_exc.SynErr(mesg=mesg)
831
+
832
+ class StreamEvent(_StreamIOMixin, threading.Event):
823
833
  '''
824
- A combination of a io.StringIO object and an asyncio.Event object.
834
+ A combination of a io.StringIO object and a threading.Event object.
825
835
  '''
826
836
  def __init__(self, *args, **kwargs):
827
- io.StringIO.__init__(self, *args, **kwargs)
828
- asyncio.Event.__init__(self)
837
+ _StreamIOMixin.__init__(self, *args, **kwargs)
838
+ threading.Event.__init__(self)
829
839
  self.mesg = ''
830
840
 
831
- def setMesg(self, mesg):
832
- '''
833
- Clear the internal event and set a new message that is used to set the event.
834
-
835
- Args:
836
- mesg (str): The string to monitor for.
841
+ def __repr__(self):
842
+ cls = self.__class__
843
+ status = 'set' if self._flag else 'unset'
844
+ if valu := str(self):
845
+ valu = s_common.trimText(valu).strip()
846
+ status = f'{status}, valu: {valu}'
847
+ return f"<{cls.__module__}.{cls.__qualname__} at {id(self):#x}: {status}>"
837
848
 
838
- Returns:
839
- None
840
- '''
841
- self.mesg = mesg
842
- self.clear()
849
+ class AsyncStreamEvent(_StreamIOMixin, asyncio.Event):
850
+ '''
851
+ A combination of a io.StringIO object and an asyncio.Event object.
852
+ '''
853
+ def __init__(self, *args, **kwargs):
854
+ _StreamIOMixin.__init__(self, *args, **kwargs)
855
+ asyncio.Event.__init__(self)
856
+ self.mesg = ''
843
857
 
844
- def write(self, s):
845
- io.StringIO.write(self, s)
846
- if self.mesg and self.mesg in s:
847
- self.set()
858
+ def __repr__(self):
859
+ cls = self.__class__
860
+ status = 'set' if self._value else 'unset'
861
+ if self._waiters:
862
+ status = f'{status}, waiters:{len(self._waiters)}'
863
+ if valu := str(self):
864
+ valu = s_common.trimText(valu).strip()
865
+ status = f'{status}, valu: {valu}'
866
+ return f"<{cls.__module__}.{cls.__qualname__} at {id(self):#x}: {status}>"
848
867
 
849
868
  async def wait(self, timeout=None):
850
869
  if timeout is None:
851
870
  return await asyncio.Event.wait(self)
852
871
  return await s_coro.event_wait(self, timeout=timeout)
853
872
 
854
- def jsonlines(self) -> typing.List[dict]:
855
- '''Get the messages as jsonlines. May throw Json errors if the captured stream is not jsonlines.'''
856
- return jsonlines(self.getvalue())
857
-
858
873
  class HttpReflector(s_httpapi.Handler):
859
874
  '''Test handler which reflects get/post data back to the caller'''
860
875
 
@@ -45,6 +45,7 @@ async def main(argv, outp=s_output.stdout):
45
45
  cert = c_x509.load_pem_x509_certificate(certbyts.encode())
46
46
  path = cdir._saveCertTo(cert, 'hosts', f'{name}.crt')
47
47
  outp.printf(f'crt saved: {path}')
48
+ cdir.delHostCsr(name, outp=outp)
48
49
  return 0
49
50
  else:
50
51
  csr = cdir.genUserCsr(name, outp=outp)
@@ -52,6 +53,7 @@ async def main(argv, outp=s_output.stdout):
52
53
  cert = c_x509.load_pem_x509_certificate(certbyts.encode())
53
54
  path = cdir._saveCertTo(cert, 'users', f'{name}.crt')
54
55
  outp.printf(f'crt saved: {path}')
56
+ cdir.delUserCsr(name, outp=outp)
55
57
  return 0
56
58
 
57
59
  def getArgParser():
@@ -98,6 +98,9 @@ async def main(argv, outp=s_output.stdout):
98
98
  teleyaml['aha:servers'] = servers
99
99
  s_common.yamlsave(teleyaml, yamlpath)
100
100
 
101
+ # cleanup CSR
102
+ certdir.delUserCsr(username)
103
+
101
104
  return 0
102
105
 
103
106
  async def _main(argv, outp=s_output.stdout): # pragma: no cover
File without changes
@@ -0,0 +1,155 @@
1
+ import os
2
+ import tarfile
3
+ import tempfile
4
+
5
+ import synapse.exc as s_exc
6
+ import synapse.common as s_common
7
+ import synapse.telepath as s_telepath
8
+
9
+ import synapse.lib.cmd as s_cmd
10
+ import synapse.lib.const as s_const
11
+ import synapse.lib.output as s_output
12
+
13
+ descr = '''
14
+ Dump blobs from a Synapse Axon.
15
+ '''
16
+
17
+ MAX_SPOOL_SIZE = s_const.mebibyte * 512
18
+ DEFAULT_ROTATE_SIZE = s_const.gigabyte * 4
19
+
20
+ def getTarName(celliden, start, end):
21
+ return f'{celliden}.{start:012d}-{end:012d}.tar.gz'
22
+
23
+ async def dumpBlobs(opts, outp):
24
+
25
+ try:
26
+ async with await s_telepath.openurl(opts.url) as axon:
27
+
28
+ cellinfo = await axon.getCellInfo()
29
+ celltype = cellinfo['cell']['type']
30
+ if "axon" not in celltype.lower():
31
+ mesg = f'Axon dump tool only works on axons, not {celltype}'
32
+ raise s_exc.TypeMismatch(mesg=mesg)
33
+ celliden = cellinfo['cell']['iden']
34
+
35
+ if os.path.exists(opts.outdir) and not os.path.isdir(opts.outdir):
36
+ raise s_exc.BadDataValu(mesg=f'Specified output directory {opts.outdir} exists but is not a directory.')
37
+ os.makedirs(opts.outdir, exist_ok=True)
38
+
39
+ if opts.statefile is None:
40
+ statefile_path = os.path.join(opts.outdir, f'{celliden}.yaml')
41
+ else:
42
+ statefile_path = opts.statefile
43
+ if os.path.isdir(statefile_path):
44
+ statefile_path = os.path.join(statefile_path, f'{celliden}.yaml')
45
+ state = {}
46
+ if os.path.isfile(statefile_path):
47
+ if (data := s_common.yamlload(statefile_path)) is not None:
48
+ state = data
49
+ opts.statefile = statefile_path
50
+
51
+ if opts.offset is not None:
52
+ start = opts.offset
53
+ else:
54
+ start = state.get('offset:next', 0)
55
+ outp.printf(f'Starting the dump from offs={start}')
56
+
57
+ rotate_size = opts.rotate_size
58
+ hashes_iter = axon.hashes(start)
59
+ last_offset = start
60
+ tar = None
61
+ tar_path = None
62
+ file_start = start
63
+ file_blobcount = 0
64
+ tar_size = 0
65
+ for_open = True
66
+
67
+ try:
68
+ async for (offs, (sha256, size)) in hashes_iter:
69
+ if for_open:
70
+ tar_path = os.path.join(opts.outdir, getTarName(celliden, offs, -1))
71
+ tar = tarfile.open(tar_path, 'w:gz')
72
+ file_start = offs
73
+ tar_size = 0
74
+ file_blobcount = 0
75
+ for_open = False
76
+
77
+ last_offset = offs
78
+ file_blobcount += 1
79
+ sha2hex = s_common.ehex(sha256)
80
+ outp.printf(f'Dumping blob {sha2hex} (size={size}, offs={offs})')
81
+
82
+ with tempfile.SpooledTemporaryFile(max_size=MAX_SPOOL_SIZE, mode='w+b', dir=opts.outdir) as tmpf:
83
+ total = 0
84
+ async for byts in axon.get(sha256):
85
+ tmpf.write(byts)
86
+ total += len(byts)
87
+ if total != size:
88
+ raise s_exc.BadDataValu(mesg=f'Blob size mismatch for {sha2hex}: expected {size}, got {total}')
89
+ tmpf.flush()
90
+ tmpf.seek(0)
91
+ tarinfo = tarfile.TarInfo(name=f"{sha2hex}.blob")
92
+ tarinfo.size = size
93
+ tar.addfile(tarinfo, tmpf)
94
+ tar_size += size
95
+
96
+ if tar_size >= rotate_size:
97
+ outp.printf(f'Rotating to new .tar.gz file at offset {offs + 1}')
98
+ tar.close()
99
+ final_name = os.path.join(opts.outdir, getTarName(celliden, file_start, offs + 1))
100
+ os.rename(tar_path, final_name)
101
+ tar = None
102
+ tar_path = None
103
+ for_open = True
104
+
105
+ if tar is not None:
106
+ tar.close()
107
+ final_name = os.path.join(opts.outdir, getTarName(celliden, file_start, last_offset + 1))
108
+ os.rename(tar_path, final_name)
109
+ tar = None
110
+ tar_path = None
111
+
112
+ state['offset:next'] = last_offset + 1
113
+ s_common.yamlsave(state, statefile_path)
114
+
115
+ finally:
116
+ if tar is not None:
117
+ tar.close()
118
+ if tar_path and os.path.isfile(tar_path):
119
+ os.remove(tar_path)
120
+
121
+ except s_exc.SynErr as exc:
122
+ mesg = exc.get('mesg')
123
+ return (False, mesg)
124
+
125
+ except Exception as e:
126
+ mesg = f'Error {e} dumping blobs from Axon url: {opts.url}'
127
+ return (False, mesg)
128
+
129
+ return (True, None)
130
+
131
+ async def main(argv, outp=s_output.stdout):
132
+ pars = s_cmd.Parser(prog='synapse.tools.axon.dump', outp=outp, description=descr)
133
+ pars.add_argument('--url', default='cell:///vertex/storage', help='Telepath URL for the Axon.')
134
+ pars.add_argument('--offset', type=int, default=None, help='Starting offset in the Axon.')
135
+ pars.add_argument('--rotate-size', type=int, default=DEFAULT_ROTATE_SIZE,
136
+ help='Rotate to a new .blobs file after the current file exceeds this size in bytes (default: 4GB). '
137
+ 'Note: files may exceed this size if a single blob is larger than the remaining space.')
138
+ pars.add_argument('--statefile', type=str, default=None,
139
+ help='Path to the state tracking file for the Axon dump.')
140
+ pars.add_argument('outdir', help='Directory to dump tar.gz files (required).')
141
+
142
+ opts = pars.parse_args(argv)
143
+
144
+ async with s_telepath.withTeleEnv():
145
+ (ok, mesg) = await dumpBlobs(opts, outp)
146
+ if not ok:
147
+ outp.printf(f'ERROR: {mesg}')
148
+ return 1
149
+
150
+ outp.printf('Successfully dumped blobs.')
151
+
152
+ return 0
153
+
154
+ if __name__ == '__main__': # pragma: no cover
155
+ s_cmd.exitmain(main)
@@ -0,0 +1,89 @@
1
+ import tarfile
2
+
3
+ import synapse.exc as s_exc
4
+ import synapse.common as s_common
5
+ import synapse.telepath as s_telepath
6
+
7
+ import synapse.lib.cmd as s_cmd
8
+ import synapse.lib.const as s_const
9
+ import synapse.lib.output as s_output
10
+
11
+ descr = '''
12
+ Load blobs into a Synapse Axon.
13
+ '''
14
+
15
+ DEFAULT_CHUNK_SIZE = s_const.mebibyte * 16
16
+
17
+ async def loadBlobs(opts, outp, tarfiles):
18
+
19
+ try:
20
+ async with await s_telepath.openurl(opts.url) as axon:
21
+
22
+ cellinfo = await axon.getCellInfo()
23
+ celltype = cellinfo['cell']['type']
24
+ if "axon" not in celltype.lower():
25
+ mesg = f'Axon load tool only works on axons, not {celltype}'
26
+ raise s_exc.TypeMismatch(mesg=mesg)
27
+
28
+ for tarfile_path in tarfiles:
29
+ outp.printf(f'Processing tar archive: {tarfile_path}')
30
+ with tarfile.open(tarfile_path, 'r:gz') as tar:
31
+ for member in tar:
32
+ if not member.name.endswith('.blob'):
33
+ continue
34
+ sha2hex = member.name[:-5]
35
+ try:
36
+ sha256 = s_common.uhex(sha2hex)
37
+ except Exception:
38
+ outp.printf(f"Skipping invalid blob filename: {member.name}")
39
+ continue
40
+ if await axon.has(sha256):
41
+ outp.printf(f"Skipping existing blob {sha2hex}")
42
+ continue
43
+ outp.printf(f"Loading blob {sha2hex} (size={member.size})")
44
+ try:
45
+ fobj = tar.extractfile(member)
46
+ except OSError as e:
47
+ outp.printf(f"WARNING: Error extracting {member.name}: {e}")
48
+ continue
49
+ if fobj is None:
50
+ outp.printf(f"Failed to extract {member.name} from tar archive.")
51
+ continue
52
+ async with await axon.upload() as upfd:
53
+ while True:
54
+ chunk = fobj.read(DEFAULT_CHUNK_SIZE)
55
+ if not chunk:
56
+ break
57
+ await upfd.write(chunk)
58
+ await upfd.save()
59
+
60
+ except s_exc.SynErr as exc:
61
+ return (False, s_exc.reprexc(exc))
62
+
63
+ except Exception as e:
64
+ mesg = f'Error {e} loading blobs into Axon url: {opts.url}'
65
+ return (False, mesg)
66
+
67
+ return (True, None)
68
+
69
+ async def main(argv, outp=s_output.stdout):
70
+ pars = s_cmd.Parser(prog='synapse.tools.axon.load', outp=outp, description=descr)
71
+ pars.add_argument('--url', default='cell:///vertex/storage', help='Telepath URL for the Axon.')
72
+ pars.add_argument('files', nargs='+', help='List of .tar.gz files to import from.')
73
+
74
+ opts = pars.parse_args(argv)
75
+
76
+ tarfiles = sorted([f for f in opts.files if f.endswith('.tar.gz')])
77
+
78
+ async with s_telepath.withTeleEnv():
79
+ (ok, mesg) = await loadBlobs(opts, outp, tarfiles)
80
+ if not ok:
81
+ outp.printf(f'ERROR: {mesg}')
82
+ return 1
83
+
84
+ outp.printf('Successfully loaded blobs.')
85
+
86
+ return 0
87
+
88
+ if __name__ == '__main__': # pragma: no cover
89
+ s_cmd.exitmain(main)
File without changes
File without changes