synapse 2.154.1__py311-none-any.whl → 2.156.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 (74) hide show
  1. synapse/cmds/cortex.py +2 -14
  2. synapse/common.py +13 -36
  3. synapse/cortex.py +15 -508
  4. synapse/lib/ast.py +215 -22
  5. synapse/lib/cell.py +35 -8
  6. synapse/lib/certdir.py +11 -0
  7. synapse/lib/cmdr.py +0 -5
  8. synapse/lib/gis.py +2 -2
  9. synapse/lib/httpapi.py +14 -43
  10. synapse/lib/layer.py +64 -201
  11. synapse/lib/lmdbslab.py +11 -0
  12. synapse/lib/node.py +1 -3
  13. synapse/lib/parser.py +10 -0
  14. synapse/lib/slabseqn.py +2 -1
  15. synapse/lib/snap.py +121 -21
  16. synapse/lib/spooled.py +9 -0
  17. synapse/lib/storm.lark +23 -6
  18. synapse/lib/storm.py +16 -339
  19. synapse/lib/storm_format.py +5 -0
  20. synapse/lib/stormhttp.py +10 -1
  21. synapse/lib/stormlib/gen.py +1 -2
  22. synapse/lib/stormlib/gis.py +41 -0
  23. synapse/lib/stormlib/graph.py +2 -1
  24. synapse/lib/stormlib/stats.py +21 -2
  25. synapse/lib/stormlib/storm.py +16 -1
  26. synapse/lib/stormtypes.py +244 -16
  27. synapse/lib/types.py +16 -2
  28. synapse/lib/version.py +2 -2
  29. synapse/lib/view.py +118 -25
  30. synapse/models/base.py +2 -2
  31. synapse/models/inet.py +60 -30
  32. synapse/models/infotech.py +130 -8
  33. synapse/models/orgs.py +3 -0
  34. synapse/models/proj.py +3 -0
  35. synapse/models/risk.py +24 -6
  36. synapse/models/syn.py +0 -38
  37. synapse/tests/test_cmds_cortex.py +1 -1
  38. synapse/tests/test_cortex.py +70 -338
  39. synapse/tests/test_lib_agenda.py +19 -54
  40. synapse/tests/test_lib_aha.py +97 -0
  41. synapse/tests/test_lib_ast.py +596 -0
  42. synapse/tests/test_lib_grammar.py +30 -10
  43. synapse/tests/test_lib_httpapi.py +33 -49
  44. synapse/tests/test_lib_layer.py +19 -234
  45. synapse/tests/test_lib_lmdbslab.py +22 -0
  46. synapse/tests/test_lib_snap.py +9 -0
  47. synapse/tests/test_lib_spooled.py +4 -0
  48. synapse/tests/test_lib_storm.py +16 -309
  49. synapse/tests/test_lib_stormlib_gis.py +21 -0
  50. synapse/tests/test_lib_stormlib_stats.py +107 -20
  51. synapse/tests/test_lib_stormlib_storm.py +25 -0
  52. synapse/tests/test_lib_stormtypes.py +253 -8
  53. synapse/tests/test_lib_types.py +40 -0
  54. synapse/tests/test_lib_view.py +6 -13
  55. synapse/tests/test_model_base.py +1 -1
  56. synapse/tests/test_model_inet.py +15 -0
  57. synapse/tests/test_model_infotech.py +110 -0
  58. synapse/tests/test_model_orgs.py +10 -0
  59. synapse/tests/test_model_person.py +0 -3
  60. synapse/tests/test_model_proj.py +2 -1
  61. synapse/tests/test_model_risk.py +24 -0
  62. synapse/tests/test_model_syn.py +20 -34
  63. synapse/tests/test_tools_csvtool.py +2 -1
  64. synapse/tests/test_tools_feed.py +4 -30
  65. synapse/tools/csvtool.py +2 -1
  66. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/METADATA +9 -9
  67. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/RECORD +70 -72
  68. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/WHEEL +1 -1
  69. synapse/cmds/cron.py +0 -726
  70. synapse/cmds/trigger.py +0 -319
  71. synapse/tests/test_cmds_cron.py +0 -453
  72. synapse/tests/test_cmds_trigger.py +0 -176
  73. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/LICENSE +0 -0
  74. {synapse-2.154.1.dist-info → synapse-2.156.0.dist-info}/top_level.txt +0 -0
synapse/cmds/cron.py DELETED
@@ -1,726 +0,0 @@
1
- import time
2
- import types
3
- import calendar
4
- import datetime
5
- import functools
6
-
7
- import synapse.exc as s_exc
8
- import synapse.common as s_common
9
-
10
- import synapse.lib.cli as s_cli
11
- import synapse.lib.cmd as s_cmd
12
- import synapse.lib.time as s_time
13
- import synapse.lib.parser as s_parser
14
-
15
- StatHelp = '''
16
- Gives detailed information about a single cron job.
17
-
18
- Syntax:
19
- cron stat <iden prefix>
20
-
21
- Notes:
22
- Any prefix that matches exactly one valid cron job iden is accepted.
23
- '''
24
-
25
- DelHelp = '''
26
- Deletes a single cron job.
27
-
28
- Syntax:
29
- cron del|rm <iden prefix>
30
-
31
- Notes:
32
- Any prefix that matches exactly one valid cron job iden is accepted.
33
- '''
34
-
35
- ListHelp = '''
36
- List existing cron jobs in a cortex.
37
-
38
- Syntax:
39
- cron list|ls
40
-
41
- Example:
42
- user iden en? rpt? now? err? # start last start last end query
43
- root 029ce7bd.. Y Y N 17863 2019-06-11T21:47 2019-06-11T21:47 exec foo
44
- root 06b46533.. Y Y N 18140 2019-06-11T21:48 2019-06-11T21:48 exec bar
45
- '''
46
-
47
- ModHelp = '''
48
- Changes an existing cron job's query.
49
-
50
- Syntax:
51
- cron mod|edit <iden prefix> <new query>
52
-
53
- Notes:
54
- Any prefix that matches exactly one valid cron iden is accepted.
55
- '''
56
-
57
- EnableHelp = '''
58
- Enable an existing cron job.
59
-
60
- Syntax:
61
- cron enable <iden prefix>
62
-
63
- Notes:
64
- Any prefix that matches exactly one valid cron iden is accepted.
65
- '''
66
-
67
- DisableHelp = '''
68
- Disable an existing cron job.
69
-
70
- Syntax:
71
- cron disable <iden prefix>
72
-
73
- Notes:
74
- Any prefix that matches exactly one valid cron iden is accepted.
75
- '''
76
-
77
- AddHelp = '''
78
- Add a recurring cron job to a cortex.
79
-
80
- Syntax:
81
- cron add [optional arguments] {query}
82
-
83
- --minute, -M int[,int...][=]
84
- --hour, -H
85
- --day, -d
86
- --month, -m
87
- --year, -y
88
-
89
- *or:*
90
-
91
- [--hourly <min> |
92
- --daily <hour>:<min> |
93
- --monthly <day>:<hour>:<min> |
94
- --yearly <month>:<day>:<hour>:<min>]
95
-
96
- Notes:
97
- All times are interpreted as UTC.
98
-
99
- All arguments are interpreted as the job period, unless the value ends in
100
- an equals sign, in which case the argument is interpreted as the recurrence
101
- period. Only one recurrence period parameter may be specified.
102
-
103
- Currently, a fixed unit must not be larger than a specified recurrence
104
- period. i.e. '--hour 7 --minute +15' (every 15 minutes from 7-8am?) is not
105
- supported.
106
-
107
- Value values for fixed hours are 0-23 on a 24-hour clock where midnight is 0.
108
-
109
- If the --day parameter value does not start with in '+' and is an integer, it is
110
- interpreted as a fixed day of the month. A negative integer may be
111
- specified to count from the end of the month with -1 meaning the last day
112
- of the month. All fixed day values are clamped to valid days, so for
113
- example '-d 31' will run on February 28.
114
-
115
- If the fixed day parameter is a value in ([Mon, Tue, Wed, Thu, Fri, Sat,
116
- Sun] if locale is set to English) it is interpreted as a fixed day of the
117
- week.
118
-
119
- Otherwise, if the parameter value starts with a '+', then it is interpreted
120
- as an recurrence interval of that many days.
121
-
122
- If no plus-sign-starting parameter is specified, the recurrence period
123
- defaults to the unit larger than all the fixed parameters. e.g. '-M 5'
124
- means every hour at 5 minutes past, and -H 3, -M 1 means 3:01 every day.
125
-
126
- At least one optional parameter must be provided.
127
-
128
- All parameters accept multiple comma-separated values. If multiple
129
- parameters have multiple values, all combinations of those values are used.
130
-
131
- All fixed units not specified lower than the recurrence period default to
132
- the lowest valid value, e.g. -m +2 will be scheduled at 12:00am the first of
133
- every other month. One exception is the largest fixed value is day of the
134
- week, then the default period is set to be a week.
135
-
136
- A month period with a day of week fixed value is not currently supported.
137
-
138
- Fixed-value year (i.e. --year 2019) is not supported. See the 'at'
139
- command for one-time cron jobs.
140
-
141
- As an alternative to the above options, one may use exactly one of
142
- --hourly, --daily, --monthly, --yearly with a colon-separated list of
143
- fixed parameters for the value. It is an error to use both the individual
144
- options and these aliases at the same time.
145
-
146
- Examples:
147
- Run a query every last day of the month at 3 am
148
- cron add -H 3 -d-1 {#foo}
149
-
150
- Run a query every 8 hours
151
- cron add -H +8 {#foo}
152
-
153
- Run a query every Wednesday and Sunday at midnight and noon
154
- cron add -H 0,12 -d Wed,Sun {#foo}
155
-
156
- Run a query every other day at 3:57pm
157
- cron add -d +2 -M 57 -H 15 {#foo}
158
- '''
159
-
160
- class Cron(s_cli.Cmd):
161
- '''
162
- Manages cron jobs in a cortex.
163
-
164
- Cron jobs are rules persistently stored in a cortex such that storm queries
165
- automatically run on a time schedule.
166
-
167
- Cron jobs may be be recurring or one-time. Use the 'at' command to add
168
- one-time jobs.
169
-
170
- A subcommand is required. Use 'cron -h' for more detailed help. '''
171
- _cmd_name = 'cron'
172
-
173
- _cmd_syntax = (
174
- ('line', {'type': 'glob'}), # type: ignore
175
- )
176
-
177
- async def _match_idens(self, core, prefix):
178
- '''
179
- Returns the iden that starts with prefix. Prints out error and returns None if it doesn't match
180
- exactly one.
181
- '''
182
- idens = [cron['iden'] for cron in await core.listCronJobs()]
183
- matches = [iden for iden in idens if iden.startswith(prefix)]
184
- if len(matches) == 1:
185
- return matches[0]
186
- elif len(matches) == 0:
187
- self.printf('Error: provided iden does not match any valid authorized cron job')
188
- else:
189
- self.printf('Error: provided iden matches more than one cron job')
190
- return None
191
-
192
- def _make_argparser(self):
193
-
194
- parser = s_cmd.Parser(prog='cron', outp=self, description=self.__doc__)
195
-
196
- subparsers = parser.add_subparsers(title='subcommands', required=True, dest='cmd',
197
- parser_class=functools.partial(s_cmd.Parser, outp=self))
198
-
199
- subparsers.add_parser('list', aliases=['ls'], help="List cron jobs you're allowed to manipulate",
200
- usage=ListHelp)
201
-
202
- parser_add = subparsers.add_parser('add', help='add a cron job', usage=AddHelp)
203
- parser_add.add_argument('--minute', '-M')
204
- parser_add.add_argument('--hour', '-H')
205
- parser_add.add_argument('--day', '-d', help='day of week, day of month or number of days')
206
- parser_add.add_argument('--month', '-m')
207
- parser_add.add_argument('--year', '-y')
208
- group = parser_add.add_mutually_exclusive_group()
209
- group.add_argument('--hourly')
210
- group.add_argument('--daily')
211
- group.add_argument('--monthly')
212
- group.add_argument('--yearly')
213
- parser_add.add_argument('query', help='Storm query in curly braces')
214
-
215
- parser_del = subparsers.add_parser('del', aliases=['rm'], help='delete a cron job', usage=DelHelp)
216
- parser_del.add_argument('prefix', help='Cron job iden prefix')
217
-
218
- parser_stat = subparsers.add_parser('stat', help='details a cron job', usage=StatHelp)
219
- parser_stat.add_argument('prefix', help='Cron job iden prefix')
220
-
221
- parser_mod = subparsers.add_parser('mod', aliases=['edit'], help='change an existing cron job', usage=ModHelp)
222
- parser_mod.add_argument('prefix', help='Cron job iden prefix')
223
- parser_mod.add_argument('query', help='New Storm query in curly braces')
224
-
225
- parser_en = subparsers.add_parser('enable', help='enable an existing cron job', usage=EnableHelp)
226
- parser_en.add_argument('prefix', help='Cron job iden prefix')
227
-
228
- parser_dis = subparsers.add_parser('disable', help='disable an existing cron job', usage=DisableHelp)
229
- parser_dis.add_argument('prefix', help='Cron job iden prefix')
230
-
231
- return parser
232
-
233
- @staticmethod
234
- def _parse_weekday(val):
235
- ''' Try to match a day-of-week abbreviation, then try a day-of-week full name '''
236
- val = val.title()
237
- try:
238
- return list(calendar.day_abbr).index(val)
239
- except ValueError:
240
- try:
241
- return list(calendar.day_name).index(val)
242
- except ValueError:
243
- return None
244
-
245
- @staticmethod
246
- def _parse_incval(incunit, incval):
247
- ''' Parse a non-day increment value. Should be an integer or a comma-separated integer list. '''
248
- try:
249
- retn = [int(val) for val in incval.split(',')]
250
- except ValueError:
251
- return None
252
-
253
- return retn[0] if len(retn) == 1 else retn
254
-
255
- @staticmethod
256
- def _parse_req(requnit, reqval):
257
- ''' Parse a non-day fixed value '''
258
- assert reqval[0] != '='
259
-
260
- try:
261
- retn = []
262
- for val in reqval.split(','):
263
- if requnit == 'month':
264
- if reqval[0].isdigit():
265
- retn.append(int(reqval)) # must be a month (1-12)
266
- else:
267
- try:
268
- retn.append(list(calendar.month_abbr).index(val.title()))
269
- except ValueError:
270
- retn.append(list(calendar.month_name).index(val.title()))
271
- else:
272
- retn.append(int(val))
273
- except ValueError:
274
- return None
275
-
276
- if not retn:
277
- return None
278
-
279
- return retn[0] if len(retn) == 1 else retn
280
-
281
- @staticmethod
282
- def _parse_day(optval):
283
- ''' Parse a --day argument '''
284
- isreq = not optval.startswith('+')
285
- if not isreq:
286
- optval = optval[1:]
287
-
288
- try:
289
- retnval = []
290
- unit = None
291
- for val in optval.split(','):
292
- if not val:
293
- raise ValueError
294
- if val[-1].isdigit():
295
- newunit = 'dayofmonth' if isreq else 'day'
296
- if unit is None:
297
- unit = newunit
298
- elif newunit != unit:
299
- raise ValueError
300
- retnval.append(int(val))
301
- else:
302
- newunit = 'dayofweek'
303
- if unit is None:
304
- unit = newunit
305
- elif newunit != unit:
306
- raise ValueError
307
-
308
- weekday = Cron._parse_weekday(val)
309
- if weekday is None:
310
- raise ValueError
311
- retnval.append(weekday)
312
- if len(retnval) == 0:
313
- raise ValueError
314
- except ValueError:
315
- return None, None
316
- if len(retnval) == 1:
317
- retnval = retnval[0]
318
- return unit, retnval
319
-
320
- def _parse_alias(self, opts):
321
- retn = types.SimpleNamespace()
322
- retn.query = opts.query
323
-
324
- if opts.hourly is not None:
325
- retn.hour = '+1'
326
- retn.minute = str(int(opts.hourly))
327
- return retn
328
-
329
- if opts.daily is not None:
330
- fields = time.strptime(opts.daily, '%H:%M')
331
- retn.day = '+1'
332
- retn.hour = str(fields.tm_hour)
333
- retn.minute = str(fields.tm_min)
334
- return retn
335
-
336
- if opts.monthly is not None:
337
- day, rest = opts.monthly.split(':', 1)
338
- fields = time.strptime(rest, '%H:%M')
339
- retn.month = '+1'
340
- retn.day = day
341
- retn.hour = str(fields.tm_hour)
342
- retn.minute = str(fields.tm_min)
343
- return retn
344
-
345
- if opts.yearly is not None:
346
- fields = opts.yearly.split(':')
347
- if len(fields) != 4:
348
- raise ValueError(f'Failed to parse parameter {opts.yearly}')
349
- retn.year = '+1'
350
- retn.month, retn.day, retn.hour, retn.minute = fields
351
- return retn
352
-
353
- return None
354
-
355
- async def _handle_add(self, core, opts):
356
- incunit = None
357
- incval = None
358
- reqdict = {}
359
- valinfo = { # unit: (minval, next largest unit)
360
- 'month': (1, 'year'),
361
- 'dayofmonth': (1, 'month'),
362
- 'hour': (0, 'day'),
363
- 'minute': (0, 'hour'),
364
- }
365
-
366
- try:
367
- alias_opts = self._parse_alias(opts)
368
- except ValueError as e:
369
- self.printf(f'Error: Failed to parse ..ly parameter: {" ".join(e.args)}')
370
- return
371
-
372
- if alias_opts:
373
- if opts.year or opts.month or opts.day or opts.hour or opts.minute:
374
- self.printf('Error: may not use both alias (..ly) and explicit options at the same time')
375
- return
376
- opts = alias_opts
377
-
378
- for optname in ('year', 'month', 'day', 'hour', 'minute'):
379
- optval = getattr(opts, optname, None)
380
-
381
- if optval is None:
382
- if incunit is None and not reqdict:
383
- continue
384
- # The option isn't set, but a higher unit is. Go ahead and set the required part to the lowest valid
385
- # value, e.g. so -m 2 would run on the *first* of every other month at midnight
386
- if optname == 'day':
387
- reqdict['dayofmonth'] = 1
388
- else:
389
- reqdict[optname] = valinfo[optname][0]
390
- continue
391
-
392
- isreq = not optval.startswith('+')
393
-
394
- if optname == 'day':
395
- unit, val = self._parse_day(optval)
396
- if val is None:
397
- self.printf(f'Error: failed to parse day value "{optval}"')
398
- return
399
- if unit == 'dayofweek':
400
- if incunit is not None:
401
- self.printf('Error: May not provide a recurrence value with day of week')
402
- return
403
- if reqdict:
404
- self.printf('Error: may not fix month or year with day of week')
405
- return
406
- incunit, incval = unit, val
407
- elif unit == 'day':
408
- incunit, incval = unit, val
409
- else:
410
- assert unit == 'dayofmonth'
411
- reqdict[unit] = val
412
- continue
413
-
414
- if not isreq:
415
- if incunit is not None:
416
- self.printf('Error: may not provide more than 1 recurrence parameter')
417
- return
418
- if reqdict:
419
- self.printf('Error: fixed unit may not be larger than recurrence unit')
420
- return
421
- incunit = optname
422
- incval = self._parse_incval(optname, optval)
423
- if incval is None:
424
- self.printf('Error: failed to parse parameter')
425
- return
426
- continue
427
-
428
- if optname == 'year':
429
- self.printf('Error: year may not be a fixed value')
430
- return
431
-
432
- reqval = self._parse_req(optname, optval)
433
- if reqval is None:
434
- self.printf(f'Error: failed to parse fixed parameter "{optval}"')
435
- return
436
- reqdict[optname] = reqval
437
-
438
- # If not set, default (incunit, incval) to (1, the next largest unit)
439
- if incunit is None:
440
- if not reqdict:
441
- self.printf('Error: must provide at least one optional argument')
442
- return
443
- requnit = next(iter(reqdict)) # the first key added is the biggest unit
444
- incunit = valinfo[requnit][1]
445
- incval = 1
446
-
447
- cdef = {'storm': opts.query,
448
- 'reqs': reqdict,
449
- 'incunit': incunit,
450
- 'incvals': incval,
451
- }
452
- newcdef = await core.addCronJob(cdef)
453
- self.printf(f'Created cron job {newcdef["iden"]}')
454
-
455
- @staticmethod
456
- def _format_timestamp(ts):
457
- return datetime.datetime.fromtimestamp(ts, datetime.UTC).strftime('%Y-%m-%dT%H:%M')
458
-
459
- async def _handle_list(self, core, opts):
460
- cronlist = await core.listCronJobs()
461
-
462
- if not cronlist:
463
- self.printf('No cron jobs found')
464
- return
465
- self.printf(
466
- f'{"user":10} {"iden":10} {"en?":3} {"rpt?":4} {"now?":4} {"err?":4} '
467
- f'{"# start":7} {"last start":16} {"last end":16} {"query"}')
468
-
469
- for cron in cronlist:
470
- iden = cron.get('iden')
471
-
472
- idenf = iden[:8] + '..'
473
- user = cron.get('username') or '<None>'
474
- query = cron.get('query') or '<missing>'
475
- isrecur = 'Y' if cron.get('recur') else 'N'
476
- isrunning = 'Y' if cron.get('isrunning') else 'N'
477
- enabled = 'Y' if cron.get('enabled') else 'N'
478
- startcount = cron.get('startcount') or 0
479
- laststart = cron.get('laststarttime')
480
- laststart = 'Never' if laststart is None else self._format_timestamp(laststart)
481
- lastend = cron.get('lastfinishtime')
482
- lastend = 'Never' if lastend is None else self._format_timestamp(lastend)
483
- result = cron.get('lastresult')
484
- iserr = 'X' if result is not None and not result.startswith('finished successfully') else ' '
485
-
486
- self.printf(
487
- f'{user:10} {idenf:10} {enabled:3} {isrecur:4} {isrunning:4} {iserr:4} '
488
- f'{startcount:7} {laststart:16} {lastend:16} {query}')
489
-
490
- async def _handle_mod(self, core, opts):
491
- prefix = opts.prefix
492
- iden = await self._match_idens(core, prefix)
493
- if iden is None:
494
- return
495
- await core.updateCronJob(iden, opts.query)
496
- self.printf(f'Modified cron job {iden}')
497
-
498
- async def _handle_enable(self, core, opts):
499
- prefix = opts.prefix
500
- iden = await self._match_idens(core, prefix)
501
- if iden is None:
502
- return
503
- await core.enableCronJob(iden)
504
- self.printf(f'Enabled cron job {iden}')
505
-
506
- async def _handle_disable(self, core, opts):
507
- prefix = opts.prefix
508
- iden = await self._match_idens(core, prefix)
509
- if iden is None:
510
- return
511
- await core.disableCronJob(iden)
512
- self.printf(f'Disabled cron job {iden}')
513
-
514
- async def _handle_del(self, core, opts):
515
- prefix = opts.prefix
516
- iden = await self._match_idens(core, prefix)
517
- if iden is None:
518
- return
519
- await core.delCronJob(iden)
520
- self.printf(f'Deleted cron job {iden}')
521
-
522
- async def _handle_stat(self, core, opts):
523
- ''' Prints details about a particular cron job. Not actually a different API call '''
524
- prefix = opts.prefix
525
- crons = await core.listCronJobs()
526
- idens = [cron['iden'] for cron in crons]
527
- matches = [iden for iden in idens if iden.startswith(prefix)]
528
- if len(matches) == 0:
529
- self.printf('Error: provided iden does not match any valid authorized cron job')
530
- return
531
- elif len(matches) > 1:
532
- self.printf('Error: provided iden matches more than one cron job')
533
- return
534
-
535
- iden = matches[0]
536
- cron = [cron for cron in crons if cron.get('iden') == iden][0]
537
-
538
- user = cron.get('username') or '<None>'
539
- query = cron.get('query') or '<missing>'
540
- isrecur = 'Yes' if cron.get('recur') else 'No'
541
- enabled = 'Yes' if cron.get('enabled') else 'No'
542
- startcount = cron.get('startcount') or 0
543
- recs = cron.get('recs', [])
544
- laststart = cron.get('laststarttime')
545
- lastend = cron.get('lastfinishtime')
546
- laststart = 'Never' if laststart is None else self._format_timestamp(laststart)
547
- lastend = 'Never' if lastend is None else self._format_timestamp(lastend)
548
- lastresult = cron.get('lastresult') or '<None>'
549
-
550
- self.printf(f'iden: {iden}')
551
- self.printf(f'user: {user}')
552
- self.printf(f'enabled: {enabled}')
553
- self.printf(f'recurring: {isrecur}')
554
- self.printf(f'# starts: {startcount}')
555
- self.printf(f'last start time: {laststart}')
556
- self.printf(f'last end time: {lastend}')
557
- self.printf(f'last result: {lastresult}')
558
- self.printf(f'query: {query}')
559
- if not recs:
560
- self.printf(f'entries: <None>')
561
- else:
562
- self.printf(f'entries: {"incunit":10} {"incval":6} {"required"}')
563
- for reqdict, incunit, incval in recs:
564
- reqdict = reqdict or '<None>'
565
- incunit = '<None>' if incunit is None else incunit
566
- incval = '<None>' if incval is None else incval
567
- self.printf(f' {incunit:10} {incval:6} {reqdict}')
568
-
569
- async def runCmdOpts(self, opts):
570
-
571
- s_common.deprdate('cmdr> cron', s_common._splicedepr)
572
-
573
- line = opts.get('line')
574
- if line is None:
575
- self.printf(self.__doc__)
576
- return
577
-
578
- core = self.getCmdItem()
579
-
580
- argv = s_parser.Parser(line).cmdrargs()
581
- try:
582
- opts = self._make_argparser().parse_args(argv)
583
- except s_exc.ParserExit:
584
- return
585
-
586
- handlers = {
587
- 'add': self._handle_add,
588
- 'del': self._handle_del,
589
- 'rm': self._handle_del,
590
- 'disable': self._handle_disable,
591
- 'enable': self._handle_enable,
592
- 'list': self._handle_list,
593
- 'ls': self._handle_list,
594
- 'mod': self._handle_mod,
595
- 'edit': self._handle_mod,
596
- 'stat': self._handle_stat,
597
- }
598
- await handlers[opts.cmd](core, opts)
599
-
600
- class At(s_cli.Cmd):
601
- '''
602
- Adds a non-recurring cron job.
603
-
604
- It will execute a Storm query at one or more specified times.
605
-
606
- List/details/deleting cron jobs created with 'at' use the same commands as
607
- other cron jobs: cron list/stat/del respectively.
608
-
609
- Syntax:
610
- at (time|+time delta)+ {query}
611
-
612
- Notes:
613
- This command accepts one or more time specifications followed by exactly
614
- one storm query in curly braces. Each time specification may be in synapse
615
- time delta format (e.g + 1 day) or synapse time format (e.g.
616
- 20501217030432101). Seconds will be ignored, as cron jobs' granularity is
617
- limited to minutes.
618
-
619
- All times are interpreted as UTC.
620
-
621
- The other option for time specification is a relative time from now. This
622
- consists of a plus sign, a positive integer, then one of 'minutes, hours,
623
- days'.
624
-
625
- Note that the record for a cron job is stored until explicitly deleted via
626
- "cron del".
627
-
628
- Examples:
629
- # Run a storm query in 5 minutes
630
- at +5 minutes {[inet:ipv4=1]}
631
-
632
- # Run a storm query tomorrow and in a week
633
- at +1 day +7 days {[inet:ipv4=1]}
634
-
635
- # Run a query at the end of the year Zulu
636
- at 20181231Z2359 {[inet:ipv4=1]}
637
- '''
638
- _cmd_name = 'at'
639
-
640
- _cmd_syntax = (
641
- ('line', {'type': 'glob'}), # type: ignore
642
- )
643
-
644
- def _make_argparser(self):
645
- parser = s_cmd.Parser(prog='at', outp=self, description=self.__doc__)
646
- parser.add_argument('args', nargs='+', help='date | delta| {query})')
647
- return parser
648
-
649
- async def runCmdOpts(self, opts):
650
-
651
- s_common.deprdate('cmdr> at', s_common._splicedepr)
652
-
653
- line = opts.get('line')
654
- if line is None:
655
- self.printf(self.__doc__)
656
- return
657
-
658
- core = self.getCmdItem()
659
-
660
- argv = s_parser.Parser(line).cmdrargs()
661
- # Currently, using an argparser is overkill for this command. Using for future extensibility (and help).
662
- try:
663
- opts = self._make_argparser().parse_args(argv)
664
- except s_exc.ParserExit:
665
- return
666
-
667
- query = None
668
- consumed_next = False
669
-
670
- tslist = []
671
- # TODO: retrieve time from cortex in case of wrong cmdr time
672
- now = time.time()
673
-
674
- query = opts.args[-1]
675
- for pos, arg in enumerate(opts.args[:-1]):
676
- try:
677
- if consumed_next:
678
- consumed_next = False
679
- continue
680
-
681
- if arg.startswith('+'):
682
- if arg[-1].isdigit():
683
- if pos == len(opts.args) - 2:
684
- self.printf('Time delta missing unit')
685
- return
686
- arg = f'{arg} {opts.args[pos + 1]}'
687
- consumed_next = True
688
- ts = now + s_time.delta(arg) / 1000.0
689
- tslist.append(ts)
690
- continue
691
-
692
- ts = s_time.parse(arg) / 1000.0
693
- tslist.append(ts)
694
-
695
- except (ValueError, s_exc.BadTypeValu):
696
- self.printf(f'Error: Trouble parsing "{arg}"')
697
- return
698
-
699
- if query is None:
700
- self.printf('Error: Missing query argument')
701
- return
702
-
703
- def _ts_to_reqdict(ts):
704
- dt = datetime.datetime.fromtimestamp(ts, datetime.timezone.utc)
705
- return {
706
- 'minute': dt.minute,
707
- 'hour': dt.hour,
708
- 'dayofmonth': dt.day,
709
- 'month': dt.month,
710
- 'year': dt.year
711
- }
712
-
713
- if not tslist:
714
- self.printf('Error: at least one requirement must be provided')
715
- return
716
-
717
- reqdicts = [_ts_to_reqdict(ts) for ts in tslist]
718
-
719
- cdef = {'storm': query,
720
- 'reqs': reqdicts,
721
- 'incunit': None,
722
- 'incvals': None,
723
- }
724
-
725
- newcdef = await core.addCronJob(cdef)
726
- self.printf(f'Created cron job {newcdef["iden"]}')