synapse 2.183.0__py311-none-any.whl → 2.185.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 (46) hide show
  1. synapse/cortex.py +1 -1
  2. synapse/datamodel.py +82 -9
  3. synapse/lib/ast.py +85 -23
  4. synapse/lib/auth.py +13 -0
  5. synapse/lib/autodoc.py +9 -2
  6. synapse/lib/cell.py +14 -1
  7. synapse/lib/modules.py +1 -0
  8. synapse/lib/parser.py +1 -0
  9. synapse/lib/snap.py +1 -0
  10. synapse/lib/storm.lark +12 -6
  11. synapse/lib/storm.py +45 -9
  12. synapse/lib/storm_format.py +1 -0
  13. synapse/lib/stormlib/graph.py +17 -0
  14. synapse/lib/stormlib/stix.py +14 -5
  15. synapse/lib/stormtypes.py +65 -37
  16. synapse/lib/stormwhois.py +3 -0
  17. synapse/lib/version.py +2 -2
  18. synapse/models/doc.py +93 -0
  19. synapse/models/infotech.py +5 -1
  20. synapse/models/media.py +0 -1
  21. synapse/models/orgs.py +102 -5
  22. synapse/models/proj.py +56 -36
  23. synapse/models/risk.py +22 -0
  24. synapse/models/syn.py +64 -6
  25. synapse/tests/test_cortex.py +54 -45
  26. synapse/tests/test_lib_ast.py +58 -0
  27. synapse/tests/test_lib_autodoc.py +54 -0
  28. synapse/tests/test_lib_cell.py +44 -1
  29. synapse/tests/test_lib_grammar.py +2 -0
  30. synapse/tests/test_lib_storm.py +68 -1
  31. synapse/tests/test_lib_stormlib_modelext.py +52 -0
  32. synapse/tests/test_lib_stormlib_stix.py +3 -2
  33. synapse/tests/test_lib_stormwhois.py +4 -4
  34. synapse/tests/test_model_doc.py +51 -0
  35. synapse/tests/test_model_infotech.py +5 -1
  36. synapse/tests/test_model_orgs.py +78 -0
  37. synapse/tests/test_model_risk.py +3 -0
  38. synapse/tests/test_model_syn.py +43 -0
  39. synapse/tests/test_tools_promote.py +67 -0
  40. synapse/tests/utils.py +26 -0
  41. synapse/tools/promote.py +14 -1
  42. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/METADATA +5 -10
  43. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/RECORD +46 -43
  44. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/WHEEL +1 -1
  45. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/LICENSE +0 -0
  46. {synapse-2.183.0.dist-info → synapse-2.185.0.dist-info}/top_level.txt +0 -0
synapse/models/proj.py CHANGED
@@ -1,14 +1,5 @@
1
1
  import synapse.lib.module as s_module
2
2
 
3
- prioenums = (
4
- (0, 'none'),
5
- (10, 'lowest'),
6
- (20, 'low'),
7
- (30, 'medium'),
8
- (40, 'high'),
9
- (50, 'highest'),
10
- )
11
-
12
3
  statusenums = (
13
4
  (0, 'new'),
14
5
  (10, 'in validation'),
@@ -47,11 +38,63 @@ class ProjectModule(s_module.CoreModule):
47
38
 
48
39
  ('proj', {
49
40
 
41
+ 'interfaces': (
42
+ ('proj:task', {
43
+
44
+ 'doc': 'A common interface for tasks.',
45
+
46
+ 'template': {
47
+ 'task': 'task'},
48
+
49
+ 'props': (
50
+
51
+ ('id', ('str', {'strip': True}), {
52
+ 'doc': 'The ID of the {task}.'}),
53
+
54
+ ('project', ('proj:project', {}), {
55
+ 'doc': 'The project containing the {task}.'}),
56
+
57
+ ('status', ('int', {}), {
58
+ # TODO: make runtime setable int enum typeopts
59
+ 'doc': 'The status of the {task}.'}),
60
+
61
+ ('priority', ('meta:priority', {}), {
62
+ 'doc': 'The priority of the {task}.'}),
63
+
64
+ ('created', ('time', {}), {
65
+ 'doc': 'The time the {task} was created.'}),
66
+
67
+ ('updated', ('time', {}), {
68
+ 'doc': 'The time the {task} was last updated.'}),
69
+
70
+ ('due', ('time', {}), {
71
+ 'doc': 'The time the {task} must be complete.'}),
72
+
73
+ ('completed', ('time', {}), {
74
+ 'doc': 'The time the {task} was completed.'}),
75
+
76
+ ('creator', ('syn:user', {}), {
77
+ 'doc': 'The user which created the {task}.'}),
78
+
79
+ ('assignee', ('syn:user', {}), {
80
+ 'doc': 'The user assigned to complete the {task}.'}),
81
+
82
+ ('ext:creator', ('ps:contact', {}), {
83
+ 'doc': 'The contact information of the creator from an external system.'}),
84
+
85
+ ('ext:assignee', ('ps:contact', {}), {
86
+ 'doc': 'The contact information of the assignee from an external system.'}),
87
+ ),
88
+ }),
89
+ ),
50
90
  'types': (
51
91
  ('proj:epic', ('guid', {}), {
52
- 'doc': 'A collection of tickets related to a topic.',
53
- }),
92
+ 'doc': 'A collection of tickets related to a topic.'}),
93
+
54
94
  ('proj:ticket', ('guid', {}), {
95
+ 'interfaces': ('proj:task',),
96
+ 'template': {
97
+ 'task': 'ticket'},
55
98
  'doc': 'A ticket in a ticketing system.'}),
56
99
 
57
100
  ('proj:project:type:taxonomy', ('taxonomy', {}), {
@@ -180,30 +223,16 @@ class ProjectModule(s_module.CoreModule):
180
223
 
181
224
  ('proj:ticket', {}, (
182
225
 
183
- ('project', ('proj:project', {}), {
184
- 'doc': 'The project containing the ticket.'}),
185
-
186
226
  ('ext:id', ('str', {'strip': True}), {
187
- 'doc': 'A ticket ID from an external system.'}),
227
+ 'deprecated': True,
228
+ 'doc': 'Deprecated. Please use :id.'}),
188
229
 
189
230
  ('ext:url', ('inet:url', {}), {
190
231
  'doc': 'A URL to the ticket in an external system.'}),
191
232
 
192
- ('ext:creator', ('ps:contact', {}), {
193
- 'doc': 'Ticket creator contact information from an external system.'}),
194
-
195
- ('ext:assignee', ('ps:contact', {}), {
196
- 'doc': 'Ticket assignee contact information from an external system.'}),
197
-
198
233
  ('epic', ('proj:epic', {}), {
199
234
  'doc': 'The epic that includes the ticket.'}),
200
235
 
201
- ('created', ('time', {}), {
202
- 'doc': 'The time the ticket was created.'}),
203
-
204
- ('updated', ('time', {'ismax': True}), {
205
- 'doc': 'The last time the ticket was updated.'}),
206
-
207
236
  ('name', ('str', {'onespace': True}), {
208
237
  'doc': 'The name of the ticket.'}),
209
238
 
@@ -219,17 +248,8 @@ class ProjectModule(s_module.CoreModule):
219
248
  ('sprint', ('proj:sprint', {}), {
220
249
  'doc': 'The sprint that contains the ticket.'}),
221
250
 
222
- ('priority', ('int', {'enums': prioenums}), {
223
- 'doc': 'The priority of the ticket.'}),
224
-
225
251
  ('type', ('str', {'lower': True, 'strip': True}), {
226
252
  'doc': 'The type of ticket. (eg story / bug)'}),
227
-
228
- ('creator', ('syn:user', {}), {
229
- 'doc': 'The synapse user who created the ticket.'}),
230
-
231
- ('assignee', ('syn:user', {}), {
232
- 'doc': 'The synapse user who the ticket is assigned to.'}),
233
253
  )),
234
254
  ),
235
255
  }),
synapse/models/risk.py CHANGED
@@ -96,12 +96,17 @@ class RiskModule(s_module.CoreModule):
96
96
  ),
97
97
  },
98
98
  }),
99
+ ('risk:mitigation:type:taxonomy', ('taxonomy', {}), {
100
+ 'interaces': ('taxonomy',),
101
+ 'doc': 'A taxonomy of mitigation types.',
102
+ }),
99
103
  ('risk:mitigation', ('guid', {}), {
100
104
  'doc': 'A mitigation for a specific risk:vuln.',
101
105
  'display': {
102
106
  'columns': (
103
107
  {'type': 'prop', 'opts': {'name': 'name'}},
104
108
  {'type': 'prop', 'opts': {'name': 'reporter:name'}},
109
+ {'type': 'prop', 'opts': {'name': 'type'}},
105
110
  {'type': 'prop', 'opts': {'name': 'tag'}},
106
111
  ),
107
112
  },
@@ -198,9 +203,22 @@ class RiskModule(s_module.CoreModule):
198
203
  'doc': 'The tool uses the target node.'}),
199
204
  (('risk:compromise', 'stole', None), {
200
205
  'doc': 'The target node was stolen or copied as a result of the compromise.'}),
206
+
201
207
  (('risk:mitigation', 'addresses', 'ou:technique'), {
202
208
  'doc': 'The mitigation addresses the technique.'}),
203
209
 
210
+ (('risk:mitigation', 'uses', 'meta:rule'), {
211
+ 'doc': 'The mitigation uses the rule.'}),
212
+
213
+ (('risk:mitigation', 'uses', 'it:app:yara:rule'), {
214
+ 'doc': 'The mitigation uses the YARA rule.'}),
215
+
216
+ (('risk:mitigation', 'uses', 'it:app:snort:rule'), {
217
+ 'doc': 'The mitigation uses the Snort rule.'}),
218
+
219
+ (('risk:mitigation', 'uses', 'inet:service:rule'), {
220
+ 'doc': 'The mitigation uses the service rule.'}),
221
+
204
222
  (('risk:leak', 'leaked', None), {
205
223
  'doc': 'The leak included the disclosure of the target node.'}),
206
224
 
@@ -334,6 +352,7 @@ class RiskModule(s_module.CoreModule):
334
352
  'doc': 'A mapping to a MITRE ATT&CK software if applicable.'}),
335
353
 
336
354
  )),
355
+ ('risk:mitigation:type:taxonomy', {}, ()),
337
356
  ('risk:mitigation', {}, (
338
357
 
339
358
  ('vuln', ('risk:vuln', {}), {
@@ -342,6 +361,9 @@ class RiskModule(s_module.CoreModule):
342
361
  ('name', ('str', {'lower': True, 'onespace': True}), {
343
362
  'doc': 'A brief name for this risk mitigation.'}),
344
363
 
364
+ ('type', ('risk:mitigation:type:taxonomy', {}), {
365
+ 'doc': 'A taxonomy type entry for the mitigation.'}),
366
+
345
367
  ('desc', ('str', {}), {
346
368
  'disp': {'hint': 'text'},
347
369
  'doc': 'A description of the mitigation approach for the vulnerability.'}),
synapse/models/syn.py CHANGED
@@ -2,10 +2,67 @@ import logging
2
2
 
3
3
  import synapse.exc as s_exc
4
4
 
5
+ import synapse.lib.types as s_types
5
6
  import synapse.lib.module as s_module
6
7
 
7
8
  logger = logging.getLogger(__name__)
8
9
 
10
+ class SynUser(s_types.Guid):
11
+
12
+ def _normPyStr(self, text):
13
+
14
+ core = self.modl.core
15
+ if core is not None:
16
+
17
+ # direct use of an iden takes precedence...
18
+ user = core.auth.user(text)
19
+ if user is not None:
20
+ return user.iden, {}
21
+
22
+ user = core.auth._getUserByName(text)
23
+ if user is not None:
24
+ return user.iden, {}
25
+
26
+ return s_types.Guid._normPyStr(self, text)
27
+
28
+ def repr(self, iden):
29
+
30
+ core = self.modl.core
31
+ if core is not None:
32
+ user = core.auth.user(iden)
33
+ if user is not None:
34
+ return user.name
35
+
36
+ return iden
37
+
38
+ class SynRole(s_types.Guid):
39
+
40
+ def _normPyStr(self, text):
41
+
42
+ core = self.modl.core
43
+ if core is not None:
44
+
45
+ # direct use of an iden takes precedence...
46
+ role = core.auth.role(text)
47
+ if role is not None:
48
+ return role.iden, {}
49
+
50
+ role = core.auth._getRoleByName(text)
51
+ if role is not None:
52
+ return role.iden, {}
53
+
54
+ return s_types.Guid._normPyStr(self, text)
55
+
56
+ def repr(self, iden):
57
+
58
+ core = self.modl.core
59
+ if core is not None:
60
+ role = core.auth.role(iden)
61
+ if role is not None:
62
+ return role.name
63
+
64
+ return iden
65
+
9
66
  class SynModule(s_module.CoreModule):
10
67
 
11
68
  def initCoreModule(self):
@@ -105,6 +162,13 @@ class SynModule(s_module.CoreModule):
105
162
 
106
163
  return (('syn', {
107
164
 
165
+ 'ctors': (
166
+ ('syn:user', 'synapse.models.syn.SynUser', {}, {
167
+ 'doc': 'A Synapse user.'}),
168
+
169
+ ('syn:role', 'synapse.models.syn.SynRole', {}, {
170
+ 'doc': 'A Synapse role.'}),
171
+ ),
108
172
  'types': (
109
173
  ('syn:type', ('str', {'strip': True}), {
110
174
  'doc': 'A Synapse type used for normalizing nodes and properties.',
@@ -130,12 +194,6 @@ class SynModule(s_module.CoreModule):
130
194
  ('syn:nodedata', ('comp', {'fields': (('key', 'str'), ('form', 'syn:form'))}), {
131
195
  'doc': 'A nodedata key and the form it may be present on.',
132
196
  }),
133
- ('syn:user', ('guid', {'strip': True}), {
134
- 'doc': 'A Synapse user GUID.'
135
- }),
136
- ('syn:role', ('guid', {'strip': True}), {
137
- 'doc': 'A Synapse role GUID.'
138
- }),
139
197
  ),
140
198
 
141
199
  'forms': (
@@ -108,6 +108,7 @@ class CortexTest(s_t_utils.SynTest):
108
108
  await core00.handoff(core00.getLocalUrl())
109
109
 
110
110
  self.false((await core00.getCellInfo())['cell']['uplink'])
111
+ self.none((await core00.getCellInfo())['cell']['mirror'])
111
112
 
112
113
  # provision with the new hostname and mirror config
113
114
  provinfo = {'mirror': '00.cortex'}
@@ -130,10 +131,13 @@ class CortexTest(s_t_utils.SynTest):
130
131
  self.true(await s_coro.event_wait(core01.nexsroot.miruplink, timeout=2))
131
132
  self.false((await core00.getCellInfo())['cell']['uplink'])
132
133
  self.true((await core01.getCellInfo())['cell']['uplink'])
134
+ self.none((await core00.getCellInfo())['cell']['mirror'])
135
+ self.eq((await core01.getCellInfo())['cell']['mirror'], 'aha://root@00.cortex...')
133
136
 
134
137
  outp = s_output.OutPutStr()
135
138
  argv = ('--svcurl', core01.getLocalUrl())
136
- await s_tools_promote.main(argv, outp=outp) # this is a graceful promotion
139
+ ret = await s_tools_promote.main(argv, outp=outp) # this is a graceful promotion
140
+ self.eq(ret, 0)
137
141
 
138
142
  self.true(core01.isactive)
139
143
  self.false(core00.isactive)
@@ -141,6 +145,10 @@ class CortexTest(s_t_utils.SynTest):
141
145
  self.true(await s_coro.event_wait(core00.nexsroot.miruplink, timeout=2))
142
146
  self.true((await core00.getCellInfo())['cell']['uplink'])
143
147
  self.false((await core01.getCellInfo())['cell']['uplink'])
148
+ # Note: The following mirror may change when SYN-7659 is addressed and greater
149
+ # control over the topology update is available during the promotion process.
150
+ self.eq((await core00.getCellInfo())['cell']['mirror'], 'aha://01.cortex.synapse')
151
+ self.none((await core01.getCellInfo())['cell']['mirror'])
144
152
 
145
153
  mods00 = s_common.yamlload(core00.dirn, 'cell.mods.yaml')
146
154
  mods01 = s_common.yamlload(core01.dirn, 'cell.mods.yaml')
@@ -2942,6 +2950,36 @@ class CortexTest(s_t_utils.SynTest):
2942
2950
  with self.raises(s_exc.NoSuchProp):
2943
2951
  await core.nodes('inet:ipv4 +:asn::_pivo::notaprop')
2944
2952
 
2953
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :email=a@v.lk]}]')
2954
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :email=b@v.lk]}]')
2955
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :email=c@v.lk]}]')
2956
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :emails=(a@v.lk, b@v.lk)]}]')
2957
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :emails=(c@v.lk, d@v.lk)]}]')
2958
+ await core.nodes('[ou:org=* :hq={[ps:contact=* :emails=(a@v.lk, d@v.lk)]}]')
2959
+
2960
+ nodes = await core.nodes('ou:org:hq::email::user=a')
2961
+ self.len(1, nodes)
2962
+ for node in nodes:
2963
+ self.eq('ou:org', node.ndef[0])
2964
+
2965
+ nodes = await core.nodes('ou:org:hq::email::user*in=(a, b)')
2966
+ self.len(2, nodes)
2967
+ for node in nodes:
2968
+ self.eq('ou:org', node.ndef[0])
2969
+
2970
+ nodes = await core.nodes('ou:org:hq::emails*[=a@v.lk]')
2971
+ self.len(2, nodes)
2972
+ for node in nodes:
2973
+ self.eq('ou:org', node.ndef[0])
2974
+
2975
+ nodes = await core.nodes('ou:org:hq::emails*[in=(a@v.lk, c@v.lk)]')
2976
+ self.len(3, nodes)
2977
+ for node in nodes:
2978
+ self.eq('ou:org', node.ndef[0])
2979
+
2980
+ with self.raises(s_exc.NoSuchProp):
2981
+ nodes = await core.nodes('ou:org:hq::email::newp=a')
2982
+
2945
2983
  class CortexBasicTest(s_t_utils.SynTest):
2946
2984
  '''
2947
2985
  The tests that are unlikely to break with different types of layers installed
@@ -3419,50 +3457,6 @@ class CortexBasicTest(s_t_utils.SynTest):
3419
3457
  self.eq(nodes[0].ndef, ('inet:ipv4', 0x01020304))
3420
3458
  self.nn(nodes[0].getTag('hehe.haha'))
3421
3459
 
3422
- async def test_storm_varlistset(self):
3423
-
3424
- async with self.getTestCore() as core:
3425
-
3426
- opts = {'vars': {'blob': ('vertex.link', '9001')}}
3427
- text = '($fqdn, $crap) = $blob [ inet:fqdn=$fqdn ]'
3428
-
3429
- nodes = await core.nodes(text, opts=opts)
3430
- self.len(1, nodes)
3431
- for node in nodes:
3432
- self.eq(node.ndef, ('inet:fqdn', 'vertex.link'))
3433
-
3434
- now = s_common.now()
3435
- ret = await core.callStorm('($foo, $bar)=$lib.cast(ival, $lib.time.now()) return($foo)')
3436
- self.ge(ret, now)
3437
-
3438
- text = '.created ($foo, $bar, $baz) = $blob'
3439
- with self.raises(s_exc.StormVarListError):
3440
- await core.nodes(text, opts)
3441
-
3442
- text = '($foo, $bar, $baz) = $blob'
3443
- with self.raises(s_exc.StormVarListError):
3444
- await core.nodes(text, opts)
3445
-
3446
- text = 'for ($x, $y) in ((1),) { $lib.print($x) }'
3447
- with self.raises(s_exc.StormVarListError):
3448
- await core.nodes(text)
3449
-
3450
- text = 'for ($x, $y) in ($lib.layer.get(),) { $lib.print($x) }'
3451
- with self.raises(s_exc.StormRuntimeError):
3452
- await core.nodes(text)
3453
-
3454
- text = '[test:str=foo] for ($x, $y) in ((1),) { $lib.print($x) }'
3455
- with self.raises(s_exc.StormVarListError):
3456
- await core.nodes(text)
3457
-
3458
- text = '[test:str=foo] for ($x, $y) in ((1),) { $lib.print($x) }'
3459
- with self.raises(s_exc.StormRuntimeError):
3460
- await core.nodes(text)
3461
-
3462
- text = '($x, $y) = (1)'
3463
- with self.raises(s_exc.StormRuntimeError):
3464
- await core.nodes(text)
3465
-
3466
3460
  async def test_storm_contbreak(self):
3467
3461
 
3468
3462
  async with self.getTestCore() as core:
@@ -3929,6 +3923,15 @@ class CortexBasicTest(s_t_utils.SynTest):
3929
3923
  opts['vars']['useriden'] = visi.iden
3930
3924
 
3931
3925
  await self.asyncraises(s_exc.AuthDeny, core.nodes('$lib.graph.del($iden2)', opts=uopts))
3926
+ await core.nodes('$lib.graph.grant($iden2, users, $useriden, 3)', opts=opts)
3927
+
3928
+ await core.nodes('$lib.graph.mod($iden2, ({"name": "newname"}))', opts=uopts)
3929
+ gdef = await core.callStorm('return($lib.graph.get($iden2))', opts=opts)
3930
+ self.eq(gdef['name'], 'newname')
3931
+
3932
+ await core.nodes('$lib.graph.revoke($iden2, users, $useriden)', opts=opts)
3933
+ await self.asyncraises(s_exc.AuthDeny, core.nodes('$lib.graph.mod($iden2, ({"name": "newp"}))', opts=uopts))
3934
+
3932
3935
  await core.nodes('$lib.graph.grant($iden2, users, $useriden, 3)', opts=opts)
3933
3936
  await core.nodes('$lib.graph.del($iden2)', opts=uopts)
3934
3937
 
@@ -4018,6 +4021,12 @@ class CortexBasicTest(s_t_utils.SynTest):
4018
4021
  async with self.getTestCore(dirn=dirn) as core:
4019
4022
  self.len(3, await core.callStorm('return($lib.graph.list())', opts=opts))
4020
4023
 
4024
+ gdef = await core.callStorm('return($lib.graph.add(({"name": "nodef"})))')
4025
+ self.eq(1, gdef['permissions']['default'])
4026
+
4027
+ gdef = await core.callStorm('return($lib.graph.add(({"name": "def", "permissions": {"default": 0}})))')
4028
+ self.eq(0, gdef['permissions']['default'])
4029
+
4021
4030
  async def test_storm_two_level_assignment(self):
4022
4031
  async with self.getTestCore() as core:
4023
4032
  q = '$foo=baz $bar=$foo [test:str=$bar]'
@@ -4331,3 +4331,61 @@ class AstTest(s_test.SynTest):
4331
4331
  _assert_edge(msgs, small, {'type': 'prop', 'prop': 'ndefs', 'reverse': True}, nidx=1)
4332
4332
  _assert_edge(msgs, small, {'type': 'edge', 'verb': 'seen', 'reverse': True}, nidx=2)
4333
4333
  _assert_edge(msgs, small, {'type': 'edge', 'verb': 'someedge', 'reverse': True}, nidx=3)
4334
+
4335
+ async def test_ast_varlistset(self):
4336
+
4337
+ async with self.getTestCore() as core:
4338
+
4339
+ opts = {'vars': {'blob': ('vertex.link', '9001')}}
4340
+ text = '($fqdn, $crap) = $blob [ inet:fqdn=$fqdn ]'
4341
+
4342
+ nodes = await core.nodes(text, opts=opts)
4343
+ self.len(1, nodes)
4344
+ for node in nodes:
4345
+ self.eq(node.ndef, ('inet:fqdn', 'vertex.link'))
4346
+
4347
+ now = s_common.now()
4348
+ ret = await core.callStorm('($foo, $bar)=$lib.cast(ival, $lib.time.now()) return($foo)')
4349
+ self.ge(ret, now)
4350
+
4351
+ # The runtsafe invocation of the VarListSetOper is done per node.
4352
+ q = '''
4353
+ init { $count = ({ 'c': (0) }) }
4354
+ function foo(){
4355
+ $count.c = ( $count.c + (1) )
4356
+ return((a, b))
4357
+ }
4358
+ inet:fqdn=vertex.link
4359
+ ($a, $b) = $foo()
4360
+ fini { return ( $count ) }
4361
+ '''
4362
+ valu = await core.callStorm(q)
4363
+ self.eq(valu, {'c': 1})
4364
+
4365
+ text = '.created ($foo, $bar, $baz) = $blob'
4366
+ with self.raises(s_exc.StormVarListError):
4367
+ await core.nodes(text, opts)
4368
+
4369
+ text = '($foo, $bar, $baz) = $blob'
4370
+ with self.raises(s_exc.StormVarListError):
4371
+ await core.nodes(text, opts)
4372
+
4373
+ text = 'for ($x, $y) in ((1),) { $lib.print($x) }'
4374
+ with self.raises(s_exc.StormVarListError):
4375
+ await core.nodes(text)
4376
+
4377
+ text = 'for ($x, $y) in ($lib.layer.get(),) { $lib.print($x) }'
4378
+ with self.raises(s_exc.StormRuntimeError):
4379
+ await core.nodes(text)
4380
+
4381
+ text = '[test:str=foo] for ($x, $y) in ((1),) { $lib.print($x) }'
4382
+ with self.raises(s_exc.StormVarListError):
4383
+ await core.nodes(text)
4384
+
4385
+ text = '[test:str=foo] for ($x, $y) in ((1),) { $lib.print($x) }'
4386
+ with self.raises(s_exc.StormRuntimeError):
4387
+ await core.nodes(text)
4388
+
4389
+ text = '($x, $y) = (1)'
4390
+ with self.raises(s_exc.StormRuntimeError):
4391
+ await core.nodes(text)
@@ -288,3 +288,57 @@ Returns:
288
288
  }
289
289
  with self.raises(s_exc.SchemaViolation):
290
290
  s_autodoc.docStormTypes(page, (doc,), linkprefix='test')
291
+
292
+ libdepr = s_t_utils.LibDepr
293
+ locls = copy.deepcopy(libdepr._storm_locals)
294
+ [obj.get('type', {}).pop('_funcname', None) for obj in locls]
295
+ doc = {
296
+ 'desc': s_stormtypes.getDoc(libdepr, "err"),
297
+ 'path': ('lib',) + libdepr._storm_lib_path,
298
+ 'locals': locls,
299
+ 'deprecated': libdepr._storm_lib_deprecation
300
+ }
301
+ page = s_autodoc.RstHelp()
302
+ page.addHead('Test')
303
+ page.addLines('I am a line.')
304
+ s_autodoc.docStormTypes(page, (doc,), linkprefix='test', islib=True)
305
+ text = page.getRstText()
306
+ expected = '''
307
+ ####
308
+ Test
309
+ ####
310
+
311
+ I am a line.
312
+
313
+
314
+ .. _test-lib-depr:
315
+
316
+ *********
317
+ $lib.depr
318
+ *********
319
+
320
+ Deprecate me!
321
+
322
+
323
+
324
+ .. _test-lib-depr-boop:
325
+
326
+ $lib.depr.boop(valu)
327
+ ====================
328
+
329
+ .. warning::
330
+ ``$lib.depr.boop`` has been deprecated and will be removed in version v3.0.0.
331
+
332
+
333
+ An example storm function that's not deprecated on its own, but the entire library is.
334
+
335
+
336
+
337
+ Args:
338
+ valu (str): What to boop.
339
+
340
+
341
+
342
+ Returns:
343
+ The booped. The return type is ``str``.'''
344
+ self.eq(text, expected)
@@ -8,7 +8,6 @@ import signal
8
8
  import socket
9
9
  import asyncio
10
10
  import tarfile
11
- import warnings
12
11
  import collections
13
12
  import multiprocessing
14
13
 
@@ -751,6 +750,7 @@ class CellTest(s_t_utils.SynTest):
751
750
  self.ge(cnfo.get('nexsindx'), 0)
752
751
  self.true(cnfo.get('active'))
753
752
  self.false(cnfo.get('uplink'))
753
+ self.none(cnfo.get('mirror', True))
754
754
  # A Cortex populated cellvers
755
755
  self.isin('cortex:defaults', cnfo.get('cellvers', {}))
756
756
 
@@ -3151,3 +3151,46 @@ class CellTest(s_t_utils.SynTest):
3151
3151
  self.isin(cell.long_lived_slab.fini, cell._fini_funcs)
3152
3152
  slabs = [s for s in cell.tofini if isinstance(s, s_lmdbslab.Slab) and s.lenv.path() == cell.short_slab_path]
3153
3153
  self.len(0, slabs)
3154
+
3155
+ async def test_lib_cell_promote_schism_prevent(self):
3156
+
3157
+ async with self.getTestAha() as aha:
3158
+ async with await s_base.Base.anit() as base:
3159
+ with self.getTestDir() as dirn:
3160
+ dirn00 = s_common.genpath(dirn, '00.cell')
3161
+ dirn01 = s_common.genpath(dirn, '01.cell')
3162
+ dirn02 = s_common.genpath(dirn, '02.cell')
3163
+
3164
+ cell00 = await base.enter_context(self.addSvcToAha(aha, '00.cell', s_cell.Cell, dirn=dirn00))
3165
+ cell01 = await base.enter_context(self.addSvcToAha(aha, '01.cell', s_cell.Cell, dirn=dirn01,
3166
+ provinfo={'mirror': 'cell'}))
3167
+ cell02 = await base.enter_context(self.addSvcToAha(aha, '02.cell', s_cell.Cell, dirn=dirn02,
3168
+ provinfo={'mirror': 'cell'}))
3169
+
3170
+ self.true(cell00.isactive)
3171
+ self.false(cell01.isactive)
3172
+ self.false(cell02.isactive)
3173
+ await cell02.sync()
3174
+
3175
+ with self.raises(s_exc.BadState) as cm:
3176
+ await cell01.handoff('some://url')
3177
+ self.isin('01.cell is not the current leader', cm.exception.get('mesg'))
3178
+
3179
+ # Note: The following behavior may change when SYN-7659 is addressed and greater
3180
+ # control over the topology update is available during the promotion process.
3181
+ # Promote 02.cell -> Promote 01.cell -> Promote 00.cell -> BadState exception
3182
+ await cell02.promote(graceful=True)
3183
+ self.false(cell00.isactive)
3184
+ self.false(cell01.isactive)
3185
+ self.true(cell02.isactive)
3186
+ await cell02.sync()
3187
+
3188
+ await cell01.promote(graceful=True)
3189
+ self.false(cell00.isactive)
3190
+ self.true(cell01.isactive)
3191
+ self.false(cell02.isactive)
3192
+ await cell02.sync()
3193
+
3194
+ with self.raises(s_exc.BadState) as cm:
3195
+ await cell00.promote(graceful=True)
3196
+ self.isin('02.cell is not the current leader', cm.exception.get('mesg'))
@@ -727,6 +727,7 @@ Queries = [
727
727
  '$p="names" ps:contact:name=foo [ :$p?-=bar ]',
728
728
  '$pvar=stuff test:arrayprop +:$pvar*[=neato]',
729
729
  '$pvar=ints test:arrayprop +:$pvar*[=$othervar]',
730
+ '$foo = ({"foo": ${ inet:fqdn }})',
730
731
  ]
731
732
 
732
733
  # Generated with print_parse_list below
@@ -1356,6 +1357,7 @@ _ParseResults = [
1356
1357
  'Query: [SetVarOper: [Const: p, Const: names], LiftPropBy: [Const: ps:contact:name, Const: =, Const: foo], EditPropSet: [RelProp: [VarValue: [Const: p]], Const: ?-=, Const: bar]]',
1357
1358
  'Query: [SetVarOper: [Const: pvar, Const: stuff], LiftProp: [Const: test:arrayprop], FiltOper: [Const: +, ArrayCond: [RelProp: [VarValue: [Const: pvar]], Const: =, Const: neato]]]',
1358
1359
  'Query: [SetVarOper: [Const: pvar, Const: ints], LiftProp: [Const: test:arrayprop], FiltOper: [Const: +, ArrayCond: [RelProp: [VarValue: [Const: pvar]], Const: =, VarValue: [Const: othervar]]]]',
1360
+ 'Query: [SetVarOper: [Const: foo, DollarExpr: [ExprDict: [Const: foo, EmbedQuery: inet:fqdn]]]]',
1359
1361
  ]
1360
1362
 
1361
1363
  class GrammarTest(s_t_utils.SynTest):