prelude-cli-beta 1405__py3-none-any.whl → 1407__py3-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 prelude-cli-beta might be problematic. Click here for more details.

Files changed (35) hide show
  1. prelude_cli_beta/cli.py +52 -0
  2. prelude_cli_beta/views/auth.py +56 -0
  3. prelude_cli_beta/views/build.py +488 -0
  4. prelude_cli_beta/views/configure.py +29 -0
  5. prelude_cli_beta/views/detect.py +438 -0
  6. prelude_cli_beta/views/generate.py +125 -0
  7. prelude_cli_beta/views/iam.py +368 -0
  8. prelude_cli_beta/views/jobs.py +50 -0
  9. prelude_cli_beta/views/partner.py +192 -0
  10. prelude_cli_beta/views/scm.py +744 -0
  11. prelude_cli_beta/views/shared.py +37 -0
  12. prelude_cli_beta-1407.dist-info/METADATA +38 -0
  13. prelude_cli_beta-1407.dist-info/RECORD +20 -0
  14. prelude_cli_beta-1407.dist-info/entry_points.txt +3 -0
  15. prelude_cli_beta-1407.dist-info/top_level.txt +1 -0
  16. prelude_cli_beta-1405.dist-info/METADATA +0 -46
  17. prelude_cli_beta-1405.dist-info/RECORD +0 -20
  18. prelude_cli_beta-1405.dist-info/top_level.txt +0 -1
  19. prelude_sdk_beta/controllers/build_controller.py +0 -309
  20. prelude_sdk_beta/controllers/detect_controller.py +0 -243
  21. prelude_sdk_beta/controllers/export_controller.py +0 -31
  22. prelude_sdk_beta/controllers/generate_controller.py +0 -40
  23. prelude_sdk_beta/controllers/http_controller.py +0 -63
  24. prelude_sdk_beta/controllers/iam_controller.py +0 -278
  25. prelude_sdk_beta/controllers/jobs_controller.py +0 -26
  26. prelude_sdk_beta/controllers/partner_controller.py +0 -166
  27. prelude_sdk_beta/controllers/probe_controller.py +0 -14
  28. prelude_sdk_beta/controllers/scm_controller.py +0 -424
  29. prelude_sdk_beta/models/account.py +0 -264
  30. prelude_sdk_beta/models/codes.py +0 -446
  31. {prelude_sdk_beta → prelude_cli_beta}/__init__.py +0 -0
  32. {prelude_sdk_beta/controllers → prelude_cli_beta/templates}/__init__.py +0 -0
  33. {prelude_sdk_beta/models → prelude_cli_beta/views}/__init__.py +0 -0
  34. {prelude_cli_beta-1405.dist-info → prelude_cli_beta-1407.dist-info}/WHEEL +0 -0
  35. {prelude_cli_beta-1405.dist-info → prelude_cli_beta-1407.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,744 @@
1
+ import click
2
+ import requests
3
+ from time import sleep
4
+
5
+ from prelude_cli_beta.views.shared import Spinner, pretty_print
6
+ from prelude_sdk_beta.controllers.export_controller import ExportController
7
+ from prelude_sdk_beta.controllers.jobs_controller import JobsController
8
+ from prelude_sdk_beta.controllers.scm_controller import ScmController
9
+ from prelude_sdk_beta.models.codes import (
10
+ Control,
11
+ ControlCategory,
12
+ PartnerEvents,
13
+ RunCode,
14
+ SCMCategory,
15
+ )
16
+
17
+
18
+ @click.group()
19
+ @click.pass_context
20
+ def scm(ctx):
21
+ """SCM system commands"""
22
+ ctx.obj = ScmController(account=ctx.obj)
23
+
24
+
25
+ @scm.command("endpoints")
26
+ @click.option(
27
+ "--limit", default=100, help="maximum number of results to return", type=int
28
+ )
29
+ @click.option("--odata_filter", help="OData filter string", default=None)
30
+ @click.option("--odata_orderby", help="OData orderby string", default=None)
31
+ @click.pass_obj
32
+ @pretty_print
33
+ def endpoints(controller, limit, odata_filter, odata_orderby):
34
+ """List endpoints with SCM data"""
35
+ with Spinner(description="Fetching endpoints from partner"):
36
+ return controller.endpoints(
37
+ filter=odata_filter, orderby=odata_orderby, top=limit
38
+ )
39
+
40
+
41
+ @scm.command("inboxes")
42
+ @click.option(
43
+ "--limit", default=100, help="maximum number of results to return", type=int
44
+ )
45
+ @click.option("--odata_filter", help="OData filter string", default=None)
46
+ @click.option("--odata_orderby", help="OData orderby string", default=None)
47
+ @click.pass_obj
48
+ @pretty_print
49
+ def endpoints(controller, limit, odata_filter, odata_orderby):
50
+ """List inboxes with SCM data"""
51
+ with Spinner(description="Fetching inboxes from partner"):
52
+ return controller.inboxes(filter=odata_filter, orderby=odata_orderby, top=limit)
53
+
54
+
55
+ @scm.command("users")
56
+ @click.option(
57
+ "--limit", default=100, help="maximum number of results to return", type=int
58
+ )
59
+ @click.option("--odata_filter", help="OData filter string", default=None)
60
+ @click.option("--odata_orderby", help="OData orderby string", default=None)
61
+ @click.pass_obj
62
+ @pretty_print
63
+ def endpoints(controller, limit, odata_filter, odata_orderby):
64
+ """List users with SCM data"""
65
+ with Spinner(description="Fetching users from partner"):
66
+ return controller.users(filter=odata_filter, orderby=odata_orderby, top=limit)
67
+
68
+
69
+ @scm.command("technique-summary")
70
+ @click.option(
71
+ "-q",
72
+ "--techniques",
73
+ help="comma-separated list of techniques to filter by",
74
+ type=str,
75
+ required=True,
76
+ )
77
+ @click.pass_obj
78
+ @pretty_print
79
+ def technique_summary(controller, techniques):
80
+ """Get policy summary per technique"""
81
+ with Spinner(description="Getting policy summary by technique"):
82
+ return controller.technique_summary(techniques=techniques)
83
+
84
+
85
+ @scm.command("evaluation-summary")
86
+ @click.option(
87
+ "--endpoint_odata_filter", help="OData filter string for endpoints", default=None
88
+ )
89
+ @click.option(
90
+ "--inbox_odata_filter", help="OData filter string for inboxes", default=None
91
+ )
92
+ @click.option("--user_odata_filter", help="OData filter string for users", default=None)
93
+ @click.option(
94
+ "-q",
95
+ "--techniques",
96
+ help="comma-separated list of techniques to filter by",
97
+ type=str,
98
+ default=None,
99
+ )
100
+ @click.pass_obj
101
+ @pretty_print
102
+ def evaluation_summary(
103
+ controller, endpoint_odata_filter, inbox_odata_filter, user_odata_filter, techniques
104
+ ):
105
+ """Get policy evaluation summary for all partners"""
106
+ with Spinner(description="Getting policy evaluation summary"):
107
+ return controller.evaluation_summary(
108
+ endpoint_filter=endpoint_odata_filter,
109
+ inbox_filter=inbox_odata_filter,
110
+ user_filter=user_odata_filter,
111
+ techniques=techniques,
112
+ )
113
+
114
+
115
+ @scm.command("evaluation")
116
+ @click.argument(
117
+ "partner",
118
+ type=click.Choice(
119
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
120
+ ),
121
+ )
122
+ @click.option("--instance_id", required=True, help="instance ID of the partner")
123
+ @click.option("--odata_filter", help="OData filter string", default=None)
124
+ @click.option(
125
+ "-q",
126
+ "--techniques",
127
+ help="comma-separated list of techniques to filter by",
128
+ type=str,
129
+ default=None,
130
+ )
131
+ @click.pass_obj
132
+ @pretty_print
133
+ def evaluation(controller, partner, instance_id, odata_filter, techniques):
134
+ """Get policy evaluation for given partner"""
135
+ with Spinner(description="Getting policy evaluation"):
136
+ return controller.evaluation(
137
+ partner=Control[partner],
138
+ instance_id=instance_id,
139
+ filter=odata_filter,
140
+ techniques=techniques,
141
+ )
142
+
143
+
144
+ @scm.command("sync")
145
+ @click.argument(
146
+ "partner",
147
+ type=click.Choice(
148
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
149
+ ),
150
+ required=True,
151
+ )
152
+ @click.option("--instance_id", required=True, help="instance ID of the partner")
153
+ @click.pass_obj
154
+ @pretty_print
155
+ def sync(controller, partner, instance_id):
156
+ """Update policy evaluation for given partner"""
157
+ with Spinner(description="Updating policy evaluation"):
158
+ job_id = controller.update_evaluation(
159
+ partner=Control[partner], instance_id=instance_id
160
+ )["job_id"]
161
+ jobs = JobsController(account=controller.account)
162
+ while (result := jobs.job_status(job_id))["end_time"] is None:
163
+ sleep(3)
164
+ return result
165
+
166
+
167
+ @scm.command("export")
168
+ @click.argument(
169
+ "type",
170
+ type=click.Choice(
171
+ [c.name for c in SCMCategory if c.value > 0], case_sensitive=False
172
+ ),
173
+ )
174
+ @click.option(
175
+ "-o",
176
+ "--output_file",
177
+ help="csv filename to export to",
178
+ type=click.Path(writable=True),
179
+ required=True,
180
+ )
181
+ @click.option(
182
+ "--limit", default=None, help="maximum number of results to return", type=int
183
+ )
184
+ @click.option("--odata_filter", help="OData filter string", default=None)
185
+ @click.option("--odata_orderby", help="OData orderby string", default=None)
186
+ @click.pass_obj
187
+ @pretty_print
188
+ def export(controller, type, output_file, limit, odata_filter, odata_orderby):
189
+ """Export SCM data"""
190
+ with Spinner(description="Exporting SCM data"):
191
+ export = ExportController(account=controller.account)
192
+ jobs = JobsController(account=controller.account)
193
+ job_id = export.export_scm(
194
+ export_type=SCMCategory[type],
195
+ filter=odata_filter,
196
+ orderby=odata_orderby,
197
+ top=limit,
198
+ )["job_id"]
199
+ while (result := jobs.job_status(job_id))["end_time"] is None:
200
+ sleep(3)
201
+ if result["successful"]:
202
+ data = requests.get(result["results"]["url"], timeout=10).content
203
+ with open(output_file, "wb") as f:
204
+ f.write(data)
205
+ return result, f"Exported data to {output_file}"
206
+
207
+
208
+ @click.group()
209
+ @click.pass_context
210
+ def group(ctx):
211
+ """SCM group commands"""
212
+ ctx.obj = ScmController(account=ctx.obj.account)
213
+
214
+
215
+ scm.add_command(group)
216
+
217
+
218
+ @group.command("list")
219
+ @click.option("--odata_filter", help="OData filter string", default=None)
220
+ @click.option("--odata_orderby", help="OData orderby string", default=None)
221
+ @click.pass_obj
222
+ @pretty_print
223
+ def list_partner_groups(controller, odata_filter, odata_orderby):
224
+ """List all partner groups"""
225
+ with Spinner(description="Fetching partner groups"):
226
+ return controller.list_partner_groups(
227
+ filter=odata_filter, orderby=odata_orderby
228
+ )
229
+
230
+
231
+ @group.command("sync")
232
+ @click.argument(
233
+ "partner",
234
+ type=click.Choice(
235
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
236
+ ),
237
+ required=True,
238
+ )
239
+ @click.option("--instance_id", required=True, help="instance ID of the partner")
240
+ @click.option("--group_ids", required=True, help="comma-separated list of group IDs")
241
+ @click.pass_obj
242
+ @pretty_print
243
+ def sync_groups(controller, partner, instance_id, group_ids):
244
+ """Update groups for a partner"""
245
+ with Spinner(description="Updating groups"):
246
+ job_id = controller.update_partner_groups(
247
+ partner=Control[partner],
248
+ instance_id=instance_id,
249
+ group_ids=group_ids.split(","),
250
+ )["job_id"]
251
+ jobs = JobsController(account=controller.account)
252
+ while (result := jobs.job_status(job_id))["end_time"] is None:
253
+ sleep(3)
254
+ return result
255
+
256
+
257
+ @click.group()
258
+ @click.pass_context
259
+ def threat(ctx):
260
+ """SCM threat commands"""
261
+ ctx.obj = ScmController(account=ctx.obj.account)
262
+
263
+
264
+ scm.add_command(threat)
265
+
266
+
267
+ @threat.command("create")
268
+ @click.argument("name")
269
+ @click.option(
270
+ "-d", "--description", help="description of the threat", default=None, type=str
271
+ )
272
+ @click.option("--id", help="uuid for threat", default=None, type=str)
273
+ @click.option(
274
+ "-g", "--generated", help="was the threat AI generated", default=False, type=bool
275
+ )
276
+ @click.option(
277
+ "-p", "--published", help="date the threat was published", default=None, type=str
278
+ )
279
+ @click.option(
280
+ "-s", "--source", help="source of threat (ex. www.cisa.gov)", default=None, type=str
281
+ )
282
+ @click.option(
283
+ "-i",
284
+ "--source_id",
285
+ help="ID of the threat, per the source (ex. aa23-075a)",
286
+ default=None,
287
+ type=str,
288
+ )
289
+ @click.option(
290
+ "-q",
291
+ "--techniques",
292
+ help="comma-separated list of techniques (MITRE ATT&CK IDs)",
293
+ default=None,
294
+ type=str,
295
+ )
296
+ @click.pass_obj
297
+ @pretty_print
298
+ def create_threat(
299
+ controller,
300
+ name,
301
+ description,
302
+ id,
303
+ generated,
304
+ published,
305
+ source,
306
+ source_id,
307
+ techniques,
308
+ ):
309
+ """Create an scm threat"""
310
+ with Spinner(description="Creating scm threat"):
311
+ return controller.create_threat(
312
+ name=name,
313
+ description=description,
314
+ id=id,
315
+ generated=generated,
316
+ published=published,
317
+ source=source,
318
+ source_id=source_id,
319
+ techniques=techniques,
320
+ )
321
+
322
+
323
+ @threat.command("delete")
324
+ @click.argument("threat_id")
325
+ @click.confirmation_option(prompt="Are you sure?")
326
+ @click.pass_obj
327
+ @pretty_print
328
+ def delete_threat(controller, threat_id):
329
+ """Delete an scm threat"""
330
+ with Spinner(description="Removing scm threat"):
331
+ return controller.delete_threat(id=threat_id)
332
+
333
+
334
+ @threat.command("list")
335
+ @click.pass_obj
336
+ @pretty_print
337
+ def list_threats(controller):
338
+ """List all scm threats"""
339
+ with Spinner(description="Fetching scm threats"):
340
+ return controller.list_threats()
341
+
342
+
343
+ @threat.command("get")
344
+ @click.argument("threat_id")
345
+ @click.pass_obj
346
+ @pretty_print
347
+ def get_threat(controller, threat_id):
348
+ """Get specific scm threat"""
349
+ with Spinner(description="Fetching scm threat"):
350
+ return controller.get_threat(id=threat_id)
351
+
352
+
353
+ @scm.command("threat-intel")
354
+ @click.argument(
355
+ "threat_pdf",
356
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
357
+ )
358
+ @click.pass_obj
359
+ @pretty_print
360
+ def parse_threat_intel(controller, threat_pdf):
361
+ with Spinner("Parsing PDF"):
362
+ return controller.parse_threat_intel(threat_pdf)
363
+
364
+
365
+ @scm.command("from-advisory")
366
+ @click.argument(
367
+ "partner", type=click.Choice([Control.CROWDSTRIKE.name], case_sensitive=False)
368
+ )
369
+ @click.option(
370
+ "-a", "--advisory_id", required=True, type=str, help="Partner advisory ID"
371
+ )
372
+ @click.pass_obj
373
+ @pretty_print
374
+ def parse_from_partner_advisory(controller, partner, advisory_id):
375
+ with Spinner("Uploading"):
376
+ return controller.parse_from_partner_advisory(
377
+ partner=Control[partner], advisory_id=advisory_id
378
+ )
379
+
380
+
381
+ @click.group()
382
+ @click.pass_context
383
+ def exception(ctx):
384
+ """SCM exception commands"""
385
+ ctx.obj = ScmController(account=ctx.obj.account)
386
+
387
+
388
+ @click.group()
389
+ @click.pass_context
390
+ def object(ctx):
391
+ """SCM object exception commands"""
392
+ ctx.obj = ScmController(account=ctx.obj.account)
393
+
394
+
395
+ @click.group()
396
+ @click.pass_context
397
+ def policy(ctx):
398
+ """SCM policy exception commands"""
399
+ ctx.obj = ScmController(account=ctx.obj.account)
400
+
401
+
402
+ exception.add_command(object)
403
+ exception.add_command(policy)
404
+ scm.add_command(exception)
405
+
406
+
407
+ @object.command("list")
408
+ @click.pass_obj
409
+ @pretty_print
410
+ def list_object_exceptions(controller):
411
+ """List all object exceptions"""
412
+ with Spinner(description="Fetching object exceptions"):
413
+ return controller.list_object_exceptions()
414
+
415
+
416
+ @object.command("create")
417
+ @click.argument(
418
+ "category",
419
+ type=click.Choice(
420
+ [
421
+ c.name
422
+ for c in ControlCategory
423
+ if c
424
+ not in [
425
+ ControlCategory.NONE,
426
+ ControlCategory.INVALID,
427
+ ControlCategory.PRIVATE_REPO,
428
+ ]
429
+ ],
430
+ case_sensitive=False,
431
+ ),
432
+ )
433
+ @click.option(
434
+ "-f", "--filter", help="OData filter string", default=None, required=True, type=str
435
+ )
436
+ @click.option(
437
+ "-e",
438
+ "--expires",
439
+ help="expiry date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
440
+ default=None,
441
+ type=str,
442
+ )
443
+ @click.option("-n", "--name", help="exception name", default=None, type=str)
444
+ @click.pass_obj
445
+ @pretty_print
446
+ def create_object_exception(controller, category, filter, expires, name):
447
+ """Create object exception"""
448
+ with Spinner(description=f"Creating object exception"):
449
+ return controller.create_object_exception(
450
+ category=ControlCategory[category],
451
+ filter=filter,
452
+ name=name,
453
+ expires=expires,
454
+ )
455
+
456
+
457
+ @object.command("update")
458
+ @click.argument("exception_id", type=str)
459
+ @click.option(
460
+ "-e",
461
+ "--expires",
462
+ help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
463
+ default=ScmController.default,
464
+ )
465
+ @click.option("-f", "--filter", help="OData filter string", default=None, type=str)
466
+ @click.option("-n", "--name", help="Exception Name", default=None, type=str)
467
+ @click.pass_obj
468
+ @pretty_print
469
+ def update_object_exception(controller, exception_id, expires, filter, name):
470
+ """Update object exception"""
471
+ with Spinner(description=f"Updating object exception"):
472
+ return controller.update_object_exception(
473
+ exception_id=exception_id, filter=filter, name=name, expires=expires
474
+ )
475
+
476
+
477
+ @object.command("delete")
478
+ @click.argument("exception_id", type=str)
479
+ @click.confirmation_option(prompt="Are you sure?")
480
+ @click.pass_obj
481
+ @pretty_print
482
+ def delete_object_exception(controller, exception_id):
483
+ """Delete object exception"""
484
+ with Spinner(description=f"Delete object exception"):
485
+ return controller.delete_object_exception(exception_id=exception_id)
486
+
487
+
488
+ @policy.command("list")
489
+ @click.pass_obj
490
+ @pretty_print
491
+ def list_policy_exceptions(controller):
492
+ """List all policy exceptions"""
493
+ with Spinner(description="Fetching policy exceptions"):
494
+ return controller.list_policy_exceptions()
495
+
496
+
497
+ @policy.command("create")
498
+ @click.argument(
499
+ "partner",
500
+ type=click.Choice(
501
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
502
+ ),
503
+ )
504
+ @click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
505
+ @click.option("-p", "--policy_id", required=True, help="ID of the policy to create")
506
+ @click.option(
507
+ "-s",
508
+ "--settings",
509
+ required=True,
510
+ help="Comma separated list of all setting names to be excluded",
511
+ )
512
+ @click.option(
513
+ "-e",
514
+ "--expires",
515
+ help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
516
+ default=None,
517
+ type=str,
518
+ )
519
+ @click.pass_obj
520
+ @pretty_print
521
+ def create_policy_exception(
522
+ controller, partner, instance_id, policy_id, settings, expires
523
+ ):
524
+ """Create policy exception"""
525
+ with Spinner(description=f"Creating policy exception"):
526
+ return controller.create_policy_exceptions(
527
+ partner=Control[partner],
528
+ expires=expires,
529
+ instance_id=instance_id,
530
+ policy_id=policy_id,
531
+ setting_names=settings.split(",") if settings else None,
532
+ )
533
+
534
+
535
+ @policy.command("update")
536
+ @click.argument(
537
+ "partner",
538
+ type=click.Choice(
539
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
540
+ ),
541
+ )
542
+ @click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
543
+ @click.option("-p", "--policy_id", required=True, help="ID of the policy to update")
544
+ @click.option(
545
+ "-e",
546
+ "--expires",
547
+ help="Expiry Date (YYYY-MM-DD hh:mm:ss ([+-]hh:mm))",
548
+ default=ScmController.default,
549
+ )
550
+ @click.option(
551
+ "-s",
552
+ "--settings",
553
+ help="Comma separated list of all setting names to be excluded",
554
+ )
555
+ @click.pass_obj
556
+ @pretty_print
557
+ def update_policy_exception(
558
+ controller, partner, instance_id, policy_id, expires, settings
559
+ ):
560
+ """Update policy exception"""
561
+ with Spinner(description=f"Updating Policy exception"):
562
+ return controller.update_policy_exception(
563
+ partner=Control[partner],
564
+ expires=expires,
565
+ instance_id=instance_id,
566
+ policy_id=policy_id,
567
+ setting_names=settings.split(",") if settings else None,
568
+ )
569
+
570
+
571
+ @policy.command("delete")
572
+ @click.argument(
573
+ "partner",
574
+ type=click.Choice(
575
+ [c.name for c in Control if c != Control.INVALID], case_sensitive=False
576
+ ),
577
+ )
578
+ @click.option("-i", "--instance_id", required=True, help="instance ID of the partner")
579
+ @click.option("-p", "--policy_id", required=True, help="ID of the policy to be deleted")
580
+ @click.confirmation_option(prompt="Are you sure?")
581
+ @click.pass_obj
582
+ @pretty_print
583
+ def delete_policy_exception(controller, partner, instance_id, policy_id):
584
+ """Delete policy exception"""
585
+ with Spinner(description=f"Deleting Policy exception"):
586
+ return controller.delete_policy_exception(
587
+ instance_id=instance_id, policy_id=policy_id
588
+ )
589
+
590
+ """Delete policy exception removes all exceptions in a policy"""
591
+ with Spinner(description=f"Deleting policy exception"):
592
+ return controller.put_policy_exceptions(
593
+ partner=Control[partner],
594
+ expires=None,
595
+ instance_id=instance_id,
596
+ policy_id=policy_id,
597
+ setting_names=[],
598
+ )
599
+
600
+
601
+ @click.group()
602
+ @click.pass_context
603
+ def notification(ctx):
604
+ """SCM notification commands"""
605
+ ctx.obj = ScmController(account=ctx.obj.account)
606
+
607
+
608
+ scm.add_command(notification)
609
+
610
+
611
+ @notification.command("list")
612
+ @click.pass_obj
613
+ @pretty_print
614
+ def list_notifications(controller):
615
+ with Spinner("Fetching notifications"):
616
+ return controller.list_notifications()
617
+
618
+
619
+ @notification.command("delete")
620
+ @click.argument("notification_id", type=str)
621
+ @click.confirmation_option(prompt="Are you sure?")
622
+ @click.pass_obj
623
+ @pretty_print
624
+ def delete_notification(controller, notification_id):
625
+ with Spinner("Deleting notification"):
626
+ return controller.delete_notification(notification_id)
627
+
628
+
629
+ @notification.command("upsert")
630
+ @click.argument(
631
+ "control_category",
632
+ type=click.Choice([c.name for c in ControlCategory], case_sensitive=False),
633
+ )
634
+ @click.option(
635
+ "-e",
636
+ "--emails",
637
+ help="comma-separated list of emails to notify",
638
+ default=None,
639
+ type=str,
640
+ )
641
+ @click.option(
642
+ "-v",
643
+ "--event",
644
+ help="event to trigger notification for",
645
+ type=click.Choice([e.name for e in PartnerEvents], case_sensitive=False),
646
+ required=True,
647
+ )
648
+ @click.option("-f", "--filter", help="OData filter string", default=None, type=str)
649
+ @click.option(
650
+ "-i", "--id", help="ID of the notification to update", default=None, type=str
651
+ )
652
+ @click.option("-m", "--message", help="notification message", default="", type=str)
653
+ @click.option(
654
+ "-r",
655
+ "--run_code",
656
+ help="notification frequency",
657
+ type=click.Choice([r.name for r in RunCode], case_sensitive=False),
658
+ required=True,
659
+ )
660
+ @click.option(
661
+ "-s",
662
+ "--scheduled_hour",
663
+ help="scheduled UTC hour to receive notifications",
664
+ type=int,
665
+ required=True,
666
+ )
667
+ @click.option(
668
+ "-u",
669
+ "--slack_urls",
670
+ help="comma-separated list of Slack Webhook URLs to notify",
671
+ default=None,
672
+ type=str,
673
+ )
674
+ @click.option(
675
+ "-p",
676
+ "--suppress_empty",
677
+ help="suppress notifications with no results",
678
+ default=True,
679
+ type=bool,
680
+ )
681
+ @click.option(
682
+ "-u",
683
+ "--teams_urls",
684
+ help="comma-separated list of Teams Webhook URLs to notify",
685
+ default=None,
686
+ type=str,
687
+ )
688
+ @click.option(
689
+ "-t", "--title", help="notification title", default="SCM Notification", type=str
690
+ )
691
+ @click.pass_obj
692
+ @pretty_print
693
+ def upsert_notification(
694
+ controller,
695
+ control_category,
696
+ emails,
697
+ event,
698
+ filter,
699
+ id,
700
+ message,
701
+ run_code,
702
+ scheduled_hour,
703
+ slack_urls,
704
+ suppress_empty,
705
+ teams_urls,
706
+ title,
707
+ ):
708
+ """Upsert an SCM notification"""
709
+ with Spinner("Upserting notification"):
710
+ return controller.upsert_notification(
711
+ control_category=ControlCategory[control_category],
712
+ emails=emails.split(",") if emails else None,
713
+ event=PartnerEvents[event],
714
+ filter=filter,
715
+ id=id,
716
+ message=message,
717
+ run_code=RunCode[run_code],
718
+ scheduled_hour=scheduled_hour,
719
+ slack_urls=slack_urls.split(",") if slack_urls else None,
720
+ suppress_empty=suppress_empty,
721
+ teams_urls=teams_urls.split(",") if teams_urls else None,
722
+ title=title,
723
+ )
724
+
725
+
726
+ @scm.command("notations")
727
+ @click.pass_obj
728
+ @pretty_print
729
+ def list_notations(controller):
730
+ """List all notations"""
731
+ with Spinner("Fetching notations"):
732
+ return controller.list_notations()
733
+
734
+
735
+ @scm.command("history")
736
+ @click.option("--odata_filter", help="OData filter string", default=None)
737
+ @click.option("--start", type=str, default=None, help="start date")
738
+ @click.option("--end", type=str, default=None, help="end date")
739
+ @click.pass_obj
740
+ @pretty_print
741
+ def list_history(controller, odata_filter, start, end):
742
+ """List history"""
743
+ with Spinner("Fetching SCM history"):
744
+ return controller.list_history(start, end, filter=odata_filter)