synapse 2.174.0__py311-none-any.whl → 2.176.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.

@@ -75,6 +75,42 @@ class DaemonTest(s_t_utils.SynTest):
75
75
  async with await s_telepath.openurl(f'tcp://127.0.0.1:{port}') as proxy:
76
76
  self.eq(proxy._ahainfo, ahainfo)
77
77
 
78
+ async def test_dmon_errors(self):
79
+
80
+ async with self.getTestCell(s_cell.Cell, conf={'dmon:listen': 'tcp://0.0.0.0:0/', 'auth:anon': 'root'}) as cell:
81
+ host, port = cell.sockaddr
82
+
83
+ async with await s_telepath.openurl(f'tcp://127.0.0.1:{port}') as prox:
84
+
85
+ # Throw an exception when trying to handle mesg outright
86
+ async with await prox.getPoolLink() as link:
87
+ with self.getAsyncLoggerStream('synapse.daemon', 'Dmon.onLinkMesg Handler: mesg=') as stream:
88
+ await link.tx(31337)
89
+ self.true(await stream.wait(timeout=6))
90
+
91
+ # Valid format; do not know what the message is.
92
+ async with await prox.getPoolLink() as link:
93
+ mesg = ('newp', {})
94
+ emsg = "Dmon.onLinkMesg Invalid mesg: mesg=('newp', {})"
95
+ with self.getAsyncLoggerStream('synapse.daemon', emsg) as stream:
96
+ await link.tx(mesg)
97
+ self.true(await stream.wait(timeout=6))
98
+
99
+ # Invalid data casues a link to fail on rx
100
+ async with await prox.getPoolLink() as link:
101
+ with self.getAsyncLoggerStream('synapse.lib.link', 'rx error') as stream:
102
+ byts = b'\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03\xa6\xa3D\xd5\xdf%\xac\xa9\x92\xc3'
103
+ await link.send(byts)
104
+ self.true(await stream.wait(timeout=6))
105
+
106
+ # bad t2:init message
107
+ async with await prox.getPoolLink() as link:
108
+ mesg = ('t2:init', {})
109
+ emsg = "Error on t2:init:"
110
+ with self.getAsyncLoggerStream('synapse.daemon', emsg) as stream:
111
+ await link.tx(mesg)
112
+ self.true(await stream.wait(timeout=6))
113
+
78
114
  class SvcApi(s_cell.CellApi, s_stormsvc.StormSvc):
79
115
  _storm_svc_name = 'foo'
80
116
  _storm_svc_pkgs = ( # type: ignore
@@ -713,6 +713,41 @@ class AstTest(s_test.SynTest):
713
713
  self.len(3, await core.nodes('test:str=ndefs :ndefs -> *'))
714
714
  self.len(2, await core.nodes('test:str=ndefs :ndefs -> it:dev:int'))
715
715
 
716
+ await core.nodes('[ risk:technique:masquerade=* :node=(it:dev:int, 1) ]')
717
+ nodes = await core.nodes('it:dev:int=1 <- *')
718
+ self.len(2, nodes)
719
+ forms = [node.ndef[0] for node in nodes]
720
+ self.sorteq(forms, ['test:str', 'risk:technique:masquerade'])
721
+
722
+ await core.nodes('risk:technique:masquerade [ :target=(it:dev:int, 1) ]')
723
+ nodes = await core.nodes('it:dev:int=1 <- *')
724
+ self.len(2, nodes)
725
+ forms = [node.ndef[0] for node in nodes]
726
+ self.sorteq(forms, ['test:str', 'risk:technique:masquerade'])
727
+
728
+ await core.nodes('risk:technique:masquerade [ :target=(it:dev:int, 2) ]')
729
+ nodes = await core.nodes('it:dev:int=1 <- *')
730
+ self.len(2, nodes)
731
+ forms = [node.ndef[0] for node in nodes]
732
+ self.sorteq(forms, ['test:str', 'risk:technique:masquerade'])
733
+
734
+ await core.nodes('risk:technique:masquerade [ -:node ]')
735
+ nodes = await core.nodes('it:dev:int=1 <- *')
736
+ self.len(1, nodes)
737
+ self.eq('test:str', nodes[0].ndef[0])
738
+
739
+ await core.nodes('test:str=ndefs [ :ndefs-=(it:dev:int, 1) ]')
740
+ self.len(0, await core.nodes('it:dev:int=1 <- *'))
741
+ nodes = await core.nodes('it:dev:int=2 <- *')
742
+ self.len(2, nodes)
743
+ forms = [node.ndef[0] for node in nodes]
744
+ self.sorteq(forms, ['test:str', 'risk:technique:masquerade'])
745
+
746
+ await core.nodes('risk:technique:masquerade [ -:target ]')
747
+ await core.nodes('test:str=ndefs [ -:ndefs ]')
748
+ self.len(0, await core.nodes('it:dev:int=1 <- *'))
749
+ self.len(0, await core.nodes('it:dev:int=2 <- *'))
750
+
716
751
  async def test_ast_pivot(self):
717
752
  # a general purpose pivot test. come on in!
718
753
  async with self.getTestCore() as core:
@@ -503,6 +503,13 @@ class CellTest(s_t_utils.SynTest):
503
503
  cell.COMMIT = 'mycommit'
504
504
  cell.VERSION = (1, 2, 3)
505
505
  cell.VERSTRING = '1.2.3'
506
+
507
+ http_info = []
508
+ host, port = await cell.addHttpsPort(0)
509
+ http_info.append({'host': host, 'port': port})
510
+ host, port = await cell.addHttpsPort(0, host='127.0.0.1')
511
+ http_info.append({'host': host, 'port': port})
512
+
506
513
  async with cell.getLocalProxy() as prox:
507
514
  info = await prox.getCellInfo()
508
515
  # Cell information
@@ -527,6 +534,10 @@ class CellTest(s_t_utils.SynTest):
527
534
  self.eq(snfo.get('verstring'), s_version.verstring),
528
535
  self.eq(snfo.get('commit'), s_version.commit)
529
536
 
537
+ netw = cnfo.get('network')
538
+ https = netw.get('https')
539
+ self.eq(https, http_info)
540
+
530
541
  async def test_cell_dyncall(self):
531
542
 
532
543
  with self.getTestDir() as dirn:
@@ -530,3 +530,32 @@ class ModelRevTest(s_tests.SynTest):
530
530
  self.len(2, nodes)
531
531
  titles = [n.ndef[1] for n in nodes]
532
532
  self.sorteq(titles, positions)
533
+
534
+ async def test_modelrev_0_2_26(self):
535
+ async with self.getRegrCore('model-0.2.26') as core:
536
+
537
+ nodes = await core.nodes('it:dev:int=1 <- *')
538
+ self.len(3, nodes)
539
+ forms = [node.ndef[0] for node in nodes]
540
+ self.sorteq(forms, ['risk:vulnerable', 'risk:vulnerable', 'inet:fqdn'])
541
+
542
+ nodes = await core.nodes('it:dev:int=2 <- *')
543
+ self.len(2, nodes)
544
+ forms = [node.ndef[0] for node in nodes]
545
+ self.sorteq(forms, ['inet:fqdn', 'inet:fqdn'])
546
+
547
+ nodes = await core.nodes('it:dev:int=3 <- *')
548
+ self.len(1, nodes)
549
+ self.eq(nodes[0].ndef[0], 'risk:vulnerable')
550
+
551
+ nodes = await core.nodes('it:dev:int=4 <- *')
552
+ self.len(1, nodes)
553
+ self.eq(nodes[0].ndef[0], 'inet:fqdn')
554
+
555
+ nodes = await core.nodes('risk:vulnerable:node=(it:dev:int, 1)')
556
+ self.len(2, nodes)
557
+
558
+ rnodes = await core.nodes('reverse(risk:vulnerable:node=(it:dev:int, 1))')
559
+ self.len(2, rnodes)
560
+
561
+ self.eq([node.ndef[0] for node in nodes], [node.ndef[0] for node in reversed(rnodes)])
@@ -1348,6 +1348,12 @@ class StormTest(s_t_utils.SynTest):
1348
1348
 
1349
1349
  self.len(0, await core.nodes('diff', opts=altview))
1350
1350
 
1351
+ await core.nodes('[ ps:contact=* :name=con0 +#con0 +#con0.foo +#conalt ]', opts=altview)
1352
+ await core.nodes('[ ps:contact=* :name=con1 +#con1 +#conalt ]', opts=altview)
1353
+
1354
+ nodes = await core.nodes('diff --tag conalt con1 con0.foo con0 newp', opts=altview)
1355
+ self.sorteq(['con0', 'con1'], [n.get('name') for n in nodes])
1356
+
1351
1357
  q = '''
1352
1358
  [ ou:name=foo +(bar)> {[ ou:name=bar ]} ]
1353
1359
  { for $i in $lib.range(1001) { $node.data.set($i, $i) }}
@@ -1537,6 +1543,112 @@ class StormTest(s_t_utils.SynTest):
1537
1543
  await core.nodes('inet:ssl:cert | merge --apply', opts=altview)
1538
1544
  self.len(1, await core.nodes('inet:ipv4'))
1539
1545
 
1546
+ async def test_storm_merge_perms(self):
1547
+
1548
+ async with self.getTestCore() as core:
1549
+
1550
+ await core.addTagProp('score', ('int', {}), {})
1551
+
1552
+ visi = await core.auth.addUser('visi')
1553
+ opts = {'user': visi.iden}
1554
+
1555
+ view2 = await core.callStorm('return($lib.view.get().fork())')
1556
+ view2opts = opts | {'view': view2['iden']}
1557
+ layr2 = view2['layers'][0]['iden']
1558
+ layr1 = view2['layers'][1]['iden']
1559
+
1560
+ await visi.addRule((True, ('view',)))
1561
+ await visi.addRule((True, ('node', 'add')), gateiden=layr2)
1562
+ await visi.addRule((True, ('node', 'prop', 'set')), gateiden=layr2)
1563
+ await visi.addRule((True, ('node', 'tag', 'add')), gateiden=layr2)
1564
+ await visi.addRule((True, ('node', 'data', 'set')), gateiden=layr2)
1565
+ await visi.addRule((True, ('node', 'edge', 'add')), gateiden=layr2)
1566
+
1567
+ await core.nodes('[ ou:name=test ]')
1568
+
1569
+ await core.nodes('''
1570
+ [ ps:contact=*
1571
+ :name=test0
1572
+ +(test)> { ou:name=test }
1573
+ +#test1.foo=now
1574
+ +#test2
1575
+ +#test3:score=42
1576
+ ]
1577
+ $node.data.set(foo, bar)
1578
+ ''', opts=view2opts)
1579
+
1580
+ with self.raises(s_exc.AuthDeny) as ecm:
1581
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1582
+ self.eq('node.del.ps:contact', ecm.exception.errinfo['perm'])
1583
+ await visi.addRule((True, ('node', 'del')), gateiden=layr2)
1584
+
1585
+ with self.raises(s_exc.AuthDeny) as ecm:
1586
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1587
+ self.eq('node.add.ps:contact', ecm.exception.errinfo['perm'])
1588
+ await visi.addRule((True, ('node', 'add')), gateiden=layr1)
1589
+
1590
+ with self.raises(s_exc.AuthDeny) as ecm:
1591
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1592
+ self.eq('node.prop.del.ps:contact..created', ecm.exception.errinfo['perm'])
1593
+ await visi.addRule((True, ('node', 'prop', 'del')), gateiden=layr2)
1594
+
1595
+ with self.raises(s_exc.AuthDeny) as ecm:
1596
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1597
+ self.eq('node.prop.set.ps:contact..created', ecm.exception.errinfo['perm'])
1598
+ await visi.addRule((True, ('node', 'prop', 'set')), gateiden=layr1)
1599
+
1600
+ with self.raises(s_exc.AuthDeny) as ecm:
1601
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1602
+ self.eq('node.tag.del.test1.foo', ecm.exception.errinfo['perm'])
1603
+ await visi.addRule((True, ('node', 'tag', 'del', 'test1', 'foo')), gateiden=layr2)
1604
+
1605
+ with self.raises(s_exc.AuthDeny) as ecm:
1606
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1607
+ self.eq('node.tag.add.test1.foo', ecm.exception.errinfo['perm'])
1608
+ await visi.addRule((True, ('node', 'tag', 'add', 'test1', 'foo')), gateiden=layr1)
1609
+
1610
+ with self.raises(s_exc.AuthDeny) as ecm:
1611
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1612
+ self.eq('node.tag.del.test3', ecm.exception.errinfo['perm'])
1613
+ await visi.addRule((True, ('node', 'tag', 'del', 'test3')), gateiden=layr2)
1614
+
1615
+ with self.raises(s_exc.AuthDeny) as ecm:
1616
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1617
+ self.eq('node.tag.add.test3', ecm.exception.errinfo['perm'])
1618
+ await visi.addRule((True, ('node', 'tag', 'add', 'test3')), gateiden=layr1)
1619
+
1620
+ with self.raises(s_exc.AuthDeny) as ecm:
1621
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1622
+ self.eq('node.tag.del.test2', ecm.exception.errinfo['perm'])
1623
+ await visi.addRule((True, ('node', 'tag', 'del', 'test2')), gateiden=layr2)
1624
+
1625
+ with self.raises(s_exc.AuthDeny) as ecm:
1626
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1627
+ self.eq('node.tag.add.test2', ecm.exception.errinfo['perm'])
1628
+ await visi.addRule((True, ('node', 'tag', 'add', 'test2')), gateiden=layr1)
1629
+
1630
+ with self.raises(s_exc.AuthDeny) as ecm:
1631
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1632
+ self.eq('node.data.pop.foo', ecm.exception.errinfo['perm'])
1633
+ await visi.addRule((True, ('node', 'data', 'pop')), gateiden=layr2)
1634
+
1635
+ with self.raises(s_exc.AuthDeny) as ecm:
1636
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1637
+ self.eq('node.data.set.foo', ecm.exception.errinfo['perm'])
1638
+ await visi.addRule((True, ('node', 'data', 'set')), gateiden=layr1)
1639
+
1640
+ with self.raises(s_exc.AuthDeny) as ecm:
1641
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1642
+ self.eq('node.edge.del.test', ecm.exception.errinfo['perm'])
1643
+ await visi.addRule((True, ('node', 'edge', 'del')), gateiden=layr2)
1644
+
1645
+ with self.raises(s_exc.AuthDeny) as ecm:
1646
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1647
+ self.eq('node.edge.add.test', ecm.exception.errinfo['perm'])
1648
+ await visi.addRule((True, ('node', 'edge', 'add')), gateiden=layr1)
1649
+
1650
+ await core.nodes('ps:contact merge --apply', opts=view2opts)
1651
+
1540
1652
  async def test_storm_movenodes(self):
1541
1653
 
1542
1654
  async with self.getTestCore() as core:
@@ -2912,27 +3024,30 @@ class StormTest(s_t_utils.SynTest):
2912
3024
 
2913
3025
  q = 'inet:ipv4=1.2.3.4 | tee --join { -> * } { <- * }'
2914
3026
  nodes = await core.nodes(q)
2915
- self.len(3, nodes)
3027
+ self.len(4, nodes)
2916
3028
  self.eq(nodes[0].ndef, ('inet:asn', 0))
2917
3029
  self.eq(nodes[1].ndef[0], ('inet:dns:a'))
2918
- self.eq(nodes[2].ndef, ('inet:ipv4', 0x01020304))
3030
+ self.eq(nodes[2].ndef[0], ('edge:refs'))
3031
+ self.eq(nodes[3].ndef, ('inet:ipv4', 0x01020304))
2919
3032
 
2920
3033
  q = 'inet:ipv4=1.2.3.4 | tee --join { -> * } { <- * } { -> edge:refs:n2 :n1 -> * }'
2921
3034
  nodes = await core.nodes(q)
2922
- self.len(4, nodes)
3035
+ self.len(5, nodes)
2923
3036
  self.eq(nodes[0].ndef, ('inet:asn', 0))
2924
3037
  self.eq(nodes[1].ndef[0], ('inet:dns:a'))
2925
- self.eq(nodes[2].ndef[0], ('media:news'))
2926
- self.eq(nodes[3].ndef, ('inet:ipv4', 0x01020304))
3038
+ self.eq(nodes[2].ndef[0], ('edge:refs'))
3039
+ self.eq(nodes[3].ndef[0], ('media:news'))
3040
+ self.eq(nodes[4].ndef, ('inet:ipv4', 0x01020304))
2927
3041
 
2928
3042
  # Queries can be a heavy list
2929
3043
  q = '$list = $lib.list(${ -> * }, ${ <- * }, ${ -> edge:refs:n2 :n1 -> * }) inet:ipv4=1.2.3.4 | tee --join $list'
2930
3044
  nodes = await core.nodes(q)
2931
- self.len(4, nodes)
3045
+ self.len(5, nodes)
2932
3046
  self.eq(nodes[0].ndef, ('inet:asn', 0))
2933
3047
  self.eq(nodes[1].ndef[0], ('inet:dns:a'))
2934
- self.eq(nodes[2].ndef[0], ('media:news'))
2935
- self.eq(nodes[3].ndef, ('inet:ipv4', 0x01020304))
3048
+ self.eq(nodes[2].ndef[0], ('edge:refs'))
3049
+ self.eq(nodes[3].ndef[0], ('media:news'))
3050
+ self.eq(nodes[4].ndef, ('inet:ipv4', 0x01020304))
2936
3051
 
2937
3052
  # A empty list of queries still works as an nop
2938
3053
  q = '$list = $lib.list() | tee $list'
@@ -2959,11 +3074,12 @@ class StormTest(s_t_utils.SynTest):
2959
3074
  q = 'inet:ipv4=1.2.3.4 | tee --join $list'
2960
3075
  queries = ('-> *', '<- *', '-> edge:refs:n2 :n1 -> *')
2961
3076
  nodes = await core.nodes(q, {'vars': {'list': queries}})
2962
- self.len(4, nodes)
3077
+ self.len(5, nodes)
2963
3078
  self.eq(nodes[0].ndef, ('inet:asn', 0))
2964
3079
  self.eq(nodes[1].ndef[0], ('inet:dns:a'))
2965
- self.eq(nodes[2].ndef[0], ('media:news'))
2966
- self.eq(nodes[3].ndef, ('inet:ipv4', 0x01020304))
3080
+ self.eq(nodes[2].ndef[0], ('edge:refs'))
3081
+ self.eq(nodes[3].ndef[0], ('media:news'))
3082
+ self.eq(nodes[4].ndef, ('inet:ipv4', 0x01020304))
2967
3083
 
2968
3084
  # Empty queries are okay - they will just return the input node
2969
3085
  q = 'inet:ipv4=1.2.3.4 | tee {}'
@@ -5,7 +5,7 @@ import synapse.tests.utils as s_test
5
5
 
6
6
  class LibStormTest(s_test.SynTest):
7
7
 
8
- async def test_lib_stormlib_storm(self):
8
+ async def test_lib_stormlib_storm_eval(self):
9
9
  async with self.getTestCore() as core:
10
10
 
11
11
  opts = {'vars': {'text': '(10)'}}
@@ -61,3 +61,84 @@ class LibStormTest(s_test.SynTest):
61
61
 
62
62
  mesg = f'Executing storm query via $lib.storm.eval() {{{q}}} as [root]'
63
63
  self.isin(mesg, data)
64
+
65
+ async def test_lib_stormlib_storm(self):
66
+
67
+ async with self.getTestCore() as core:
68
+
69
+ q = '''
70
+ $query = '[ inet:fqdn=foo.com inet:fqdn=bar.com ]'
71
+ storm.exec $query
72
+ '''
73
+ self.len(2, await core.nodes(q))
74
+
75
+ q = '''[
76
+ (inet:ipv4=1.2.3.4 :asn=4)
77
+ (inet:ipv4=1.2.3.5 :asn=5)
78
+ (inet:ipv4=1.2.3.6 :asn=10)
79
+ ]'''
80
+ await core.nodes(q)
81
+
82
+ q = '''
83
+ $filter = '-:asn=10'
84
+ inet:ipv4:asn
85
+ storm.exec $filter
86
+ '''
87
+ nodes = await core.nodes(q)
88
+ self.len(2, nodes)
89
+ for node in nodes:
90
+ self.ne(node.get('asn'), 10)
91
+
92
+ q = '''
93
+ $pivot = ${ -> inet:asn }
94
+ inet:ipv4:asn
95
+ storm.exec $pivot
96
+ '''
97
+ nodes = await core.nodes(q)
98
+ self.len(3, nodes)
99
+ for node in nodes:
100
+ self.eq(node.form.name, 'inet:asn')
101
+
102
+ # Exec a non-runtsafe query
103
+ q = '''
104
+ inet:ipv4:asn
105
+ $filter = `+:asn={$node.repr().split('.').'-1'}`
106
+ storm.exec $filter
107
+ '''
108
+ nodes = await core.nodes(q)
109
+ self.len(2, nodes)
110
+ for node in nodes:
111
+ self.ne(node.get('asn'), 10)
112
+
113
+ iden = await core.callStorm('return($lib.view.get().fork().iden)')
114
+ msgs = await core.stormlist('''
115
+ $query = "[inet:fqdn=vertex.link +#haha] $lib.print(woot)"
116
+ $opts = ({"view": $view})
117
+ for $mesg in $lib.storm.run($query, opts=$opts) {
118
+ if ($mesg.0 = "print") { $lib.print($mesg.1.mesg) }
119
+ }
120
+ ''', opts={'vars': {'view': iden}})
121
+ self.stormIsInPrint('woot', msgs)
122
+ self.len(1, await core.nodes('inet:fqdn#haha', opts={'view': iden}))
123
+
124
+ visi = await core.auth.addUser('visi')
125
+ msgs = await core.stormlist('''
126
+ $opts=({"user": $lib.auth.users.byname(root).iden})
127
+ for $mesg in $lib.storm.run("$lib.print(lolz)", opts=$opts) {
128
+ if ($mesg.0 = "err") { $lib.print($mesg) }
129
+ if ($mesg.0 = "print") { $lib.print($mesg) }
130
+ }
131
+ ''', opts={'user': visi.iden})
132
+ self.stormIsInErr('must have permission impersonate', msgs)
133
+ self.stormNotInPrint('lolz', msgs)
134
+
135
+ # no opts provided
136
+ msgs = await core.stormlist('''
137
+ $q = ${ $lib.print('hello') }
138
+ for $mesg in $lib.storm.run($q) {
139
+ if ( $mesg.0 = 'print' ) {
140
+ $lib.print(`mesg={$mesg.1.mesg}`)
141
+ }
142
+ }
143
+ ''')
144
+ self.stormIsInPrint('mesg=hello', msgs)
@@ -3300,3 +3300,34 @@ class InetModelTest(s_t_utils.SynTest):
3300
3300
  self.eq(nodes[0].get('resource'), resource.ndef[1])
3301
3301
  self.true(nodes[0].get('success'))
3302
3302
  self.eq(nodes[0].get('time'), 1715856900000)
3303
+
3304
+ q = '''
3305
+ [ inet:service:message=(visi, says, relax)
3306
+ :title="Hehe Haha"
3307
+ :thread={[
3308
+ inet:service:thread=*
3309
+ :title="Woot Woot"
3310
+ :message=(visi, says, hello)
3311
+ :channel={[
3312
+ inet:service:channel=(synapse, subreddit)
3313
+ :name="/r/synapse"
3314
+ ]}
3315
+ ]}
3316
+ ]
3317
+ '''
3318
+ nodes = await core.nodes(q)
3319
+ self.len(1, nodes)
3320
+ self.len(1, await core.nodes('inet:service:message=(visi, says, hello) -> inet:service:thread:message'))
3321
+ self.len(1, await core.nodes('''
3322
+ inet:service:message:title="hehe haha"
3323
+ :thread -> inet:service:thread
3324
+ +:title="woot woot"
3325
+ '''))
3326
+ self.len(2, await core.nodes('inet:service:thread -> inet:service:message'))
3327
+
3328
+ self.len(1, await core.nodes('''
3329
+ inet:service:message:title="hehe haha"
3330
+ :thread -> inet:service:thread
3331
+ :channel -> inet:service:channel
3332
+ +:name="/r/synapse"
3333
+ '''))
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import re
2
3
  import sys
3
4
  import pprint
4
5
  import asyncio
@@ -41,8 +42,6 @@ def gen(opts: argparse.Namespace,
41
42
  data['type'] = opts.type
42
43
  data['desc'] = opts.desc
43
44
 
44
- if opts.migration_desc:
45
- data['migration_desc'] = opts.migration_desc
46
45
  if opts.pr:
47
46
  data['prs'] = [opts.pr]
48
47
 
@@ -104,19 +103,40 @@ def format(opts: argparse.Namespace,
104
103
 
105
104
  s_schemas._reqChanglogSchema(data)
106
105
 
106
+ data.setdefault('prs', [])
107
+ prs = data.get('prs')
108
+
107
109
  if opts.prs_from_git:
108
- outp.printf('--prs-from-git not yet implemented.')
109
- return 1
110
110
 
111
- if opts.enforce_prs and not data.get('prs'):
111
+ argv = ['git', 'log', '--pretty=oneline', fp]
112
+ ret = subprocess.run(argv, capture_output=True)
113
+ if opts.verbose:
114
+ outp.printf(f'stddout={ret.stdout}')
115
+ outp.printf(f'stderr={ret.stderr}')
116
+ ret.check_returncode()
117
+
118
+ for line in ret.stdout.splitlines():
119
+ line = line.decode()
120
+ line = line.strip()
121
+ if not line:
122
+ continue
123
+ match = re.search('\\(#(?P<pr>\\d{1,})\\)', line)
124
+ if match:
125
+ for pr in match.groups():
126
+ pr = int(pr)
127
+ if pr not in prs:
128
+ prs.append(pr)
129
+ if opts.verbose:
130
+ outp.printf(f'Added PR #{pr} to the pr list from [{line=}]')
131
+
132
+ if opts.enforce_prs and not prs:
112
133
  outp.printf(f'Entry is missing PR numbers: {fp=}')
113
134
  return 1
114
- data.setdefault('prs', [])
115
135
 
116
136
  if opts.verbose:
117
137
  outp.printf(f'Got data from {fp=}')
118
138
 
119
- data.get('prs').sort() # sort the PRs inplace
139
+ prs.sort() # sort the PRs inplace
120
140
  entries[data.get('type')].append(data)
121
141
 
122
142
  if not entries:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: synapse
3
- Version: 2.174.0
3
+ Version: 2.176.0
4
4
  Summary: Synapse Intelligence Analysis Framework
5
5
  Author-email: The Vertex Project LLC <root@vertex.link>
6
6
  License: Apache License 2.0