synapse 2.155.0__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.
- synapse/cmds/cortex.py +2 -14
- synapse/common.py +1 -28
- synapse/cortex.py +10 -510
- synapse/lib/ast.py +60 -1
- synapse/lib/cell.py +33 -8
- synapse/lib/certdir.py +11 -0
- synapse/lib/cmdr.py +0 -5
- synapse/lib/gis.py +2 -2
- synapse/lib/httpapi.py +1 -43
- synapse/lib/layer.py +64 -201
- synapse/lib/lmdbslab.py +11 -0
- synapse/lib/node.py +1 -3
- synapse/lib/parser.py +10 -0
- synapse/lib/snap.py +121 -21
- synapse/lib/storm.lark +23 -6
- synapse/lib/storm.py +15 -338
- synapse/lib/storm_format.py +5 -0
- synapse/lib/stormlib/gen.py +1 -2
- synapse/lib/stormlib/gis.py +41 -0
- synapse/lib/stormlib/stats.py +21 -2
- synapse/lib/stormlib/storm.py +16 -1
- synapse/lib/stormtypes.py +225 -12
- synapse/lib/version.py +2 -2
- synapse/lib/view.py +96 -21
- synapse/models/inet.py +60 -30
- synapse/models/infotech.py +56 -1
- synapse/models/orgs.py +3 -0
- synapse/models/risk.py +15 -0
- synapse/models/syn.py +0 -38
- synapse/tests/test_cmds_cortex.py +1 -1
- synapse/tests/test_cortex.py +32 -336
- synapse/tests/test_lib_agenda.py +19 -54
- synapse/tests/test_lib_aha.py +97 -0
- synapse/tests/test_lib_ast.py +402 -0
- synapse/tests/test_lib_grammar.py +30 -10
- synapse/tests/test_lib_httpapi.py +0 -46
- synapse/tests/test_lib_layer.py +19 -234
- synapse/tests/test_lib_lmdbslab.py +22 -0
- synapse/tests/test_lib_snap.py +9 -0
- synapse/tests/test_lib_storm.py +16 -309
- synapse/tests/test_lib_stormlib_gis.py +21 -0
- synapse/tests/test_lib_stormlib_stats.py +107 -20
- synapse/tests/test_lib_stormlib_storm.py +25 -0
- synapse/tests/test_lib_stormtypes.py +231 -8
- synapse/tests/test_lib_view.py +6 -13
- synapse/tests/test_model_base.py +1 -1
- synapse/tests/test_model_inet.py +15 -0
- synapse/tests/test_model_infotech.py +60 -0
- synapse/tests/test_model_orgs.py +10 -0
- synapse/tests/test_model_person.py +0 -3
- synapse/tests/test_model_risk.py +20 -0
- synapse/tests/test_model_syn.py +20 -34
- synapse/tests/test_tools_csvtool.py +2 -1
- synapse/tests/test_tools_feed.py +4 -30
- synapse/tools/csvtool.py +2 -1
- {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/METADATA +3 -3
- {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/RECORD +60 -62
- {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/WHEEL +1 -1
- synapse/cmds/cron.py +0 -726
- synapse/cmds/trigger.py +0 -319
- synapse/tests/test_cmds_cron.py +0 -453
- synapse/tests/test_cmds_trigger.py +0 -176
- {synapse-2.155.0.dist-info → synapse-2.156.0.dist-info}/LICENSE +0 -0
- {synapse-2.155.0.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"]}')
|