contentctl 4.4.7__py3-none-any.whl → 5.0.0__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.
Files changed (123) hide show
  1. contentctl/__init__.py +1 -1
  2. contentctl/actions/build.py +102 -57
  3. contentctl/actions/deploy_acs.py +29 -24
  4. contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
  5. contentctl/actions/detection_testing/GitService.py +134 -76
  6. contentctl/actions/detection_testing/generate_detection_coverage_badge.py +48 -30
  7. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
  8. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
  9. contentctl/actions/detection_testing/progress_bar.py +9 -6
  10. contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
  11. contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
  12. contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
  13. contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
  14. contentctl/actions/doc_gen.py +9 -5
  15. contentctl/actions/initialize.py +45 -33
  16. contentctl/actions/inspect.py +118 -61
  17. contentctl/actions/new_content.py +155 -108
  18. contentctl/actions/release_notes.py +276 -146
  19. contentctl/actions/reporting.py +23 -19
  20. contentctl/actions/test.py +33 -28
  21. contentctl/actions/validate.py +55 -34
  22. contentctl/api.py +54 -45
  23. contentctl/contentctl.py +124 -90
  24. contentctl/enrichments/attack_enrichment.py +112 -72
  25. contentctl/enrichments/cve_enrichment.py +34 -28
  26. contentctl/enrichments/splunk_app_enrichment.py +38 -36
  27. contentctl/helper/link_validator.py +101 -78
  28. contentctl/helper/splunk_app.py +69 -41
  29. contentctl/helper/utils.py +58 -53
  30. contentctl/input/director.py +68 -36
  31. contentctl/input/new_content_questions.py +27 -35
  32. contentctl/input/yml_reader.py +28 -18
  33. contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
  34. contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
  35. contentctl/objects/alert_action.py +10 -9
  36. contentctl/objects/annotated_types.py +1 -1
  37. contentctl/objects/atomic.py +65 -54
  38. contentctl/objects/base_test.py +5 -3
  39. contentctl/objects/base_test_result.py +19 -11
  40. contentctl/objects/baseline.py +62 -30
  41. contentctl/objects/baseline_tags.py +30 -24
  42. contentctl/objects/config.py +790 -597
  43. contentctl/objects/constants.py +33 -56
  44. contentctl/objects/correlation_search.py +150 -136
  45. contentctl/objects/dashboard.py +55 -41
  46. contentctl/objects/data_source.py +16 -17
  47. contentctl/objects/deployment.py +43 -44
  48. contentctl/objects/deployment_email.py +3 -2
  49. contentctl/objects/deployment_notable.py +4 -2
  50. contentctl/objects/deployment_phantom.py +7 -6
  51. contentctl/objects/deployment_rba.py +3 -2
  52. contentctl/objects/deployment_scheduling.py +3 -2
  53. contentctl/objects/deployment_slack.py +3 -2
  54. contentctl/objects/detection.py +5 -2
  55. contentctl/objects/detection_metadata.py +1 -0
  56. contentctl/objects/detection_stanza.py +7 -2
  57. contentctl/objects/detection_tags.py +58 -103
  58. contentctl/objects/drilldown.py +66 -34
  59. contentctl/objects/enums.py +81 -100
  60. contentctl/objects/errors.py +16 -24
  61. contentctl/objects/integration_test.py +3 -3
  62. contentctl/objects/integration_test_result.py +1 -0
  63. contentctl/objects/investigation.py +59 -36
  64. contentctl/objects/investigation_tags.py +30 -19
  65. contentctl/objects/lookup.py +304 -101
  66. contentctl/objects/macro.py +55 -39
  67. contentctl/objects/manual_test.py +3 -3
  68. contentctl/objects/manual_test_result.py +1 -0
  69. contentctl/objects/mitre_attack_enrichment.py +17 -16
  70. contentctl/objects/notable_action.py +2 -1
  71. contentctl/objects/notable_event.py +1 -3
  72. contentctl/objects/playbook.py +37 -35
  73. contentctl/objects/playbook_tags.py +23 -13
  74. contentctl/objects/rba.py +96 -0
  75. contentctl/objects/risk_analysis_action.py +15 -11
  76. contentctl/objects/risk_event.py +110 -160
  77. contentctl/objects/risk_object.py +1 -0
  78. contentctl/objects/savedsearches_conf.py +9 -7
  79. contentctl/objects/security_content_object.py +5 -2
  80. contentctl/objects/story.py +54 -49
  81. contentctl/objects/story_tags.py +56 -45
  82. contentctl/objects/test_attack_data.py +2 -1
  83. contentctl/objects/test_group.py +5 -2
  84. contentctl/objects/threat_object.py +1 -0
  85. contentctl/objects/throttling.py +27 -18
  86. contentctl/objects/unit_test.py +3 -4
  87. contentctl/objects/unit_test_baseline.py +5 -5
  88. contentctl/objects/unit_test_result.py +6 -6
  89. contentctl/output/api_json_output.py +233 -220
  90. contentctl/output/attack_nav_output.py +21 -21
  91. contentctl/output/attack_nav_writer.py +29 -37
  92. contentctl/output/conf_output.py +235 -172
  93. contentctl/output/conf_writer.py +201 -125
  94. contentctl/output/data_source_writer.py +38 -26
  95. contentctl/output/doc_md_output.py +53 -27
  96. contentctl/output/jinja_writer.py +19 -15
  97. contentctl/output/json_writer.py +21 -11
  98. contentctl/output/svg_output.py +56 -38
  99. contentctl/output/templates/analyticstories_detections.j2 +2 -2
  100. contentctl/output/templates/analyticstories_stories.j2 +1 -1
  101. contentctl/output/templates/collections.j2 +1 -1
  102. contentctl/output/templates/doc_detections.j2 +0 -5
  103. contentctl/output/templates/es_investigations_investigations.j2 +1 -1
  104. contentctl/output/templates/es_investigations_stories.j2 +1 -1
  105. contentctl/output/templates/savedsearches_baselines.j2 +2 -2
  106. contentctl/output/templates/savedsearches_detections.j2 +10 -11
  107. contentctl/output/templates/savedsearches_investigations.j2 +2 -2
  108. contentctl/output/templates/transforms.j2 +6 -8
  109. contentctl/output/yml_writer.py +29 -20
  110. contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
  111. contentctl/templates/stories/cobalt_strike.yml +1 -0
  112. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/METADATA +5 -4
  113. contentctl-5.0.0.dist-info/RECORD +168 -0
  114. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/WHEEL +1 -1
  115. contentctl/actions/initialize_old.py +0 -245
  116. contentctl/objects/event_source.py +0 -11
  117. contentctl/objects/observable.py +0 -37
  118. contentctl/output/detection_writer.py +0 -28
  119. contentctl/output/new_content_yml_output.py +0 -56
  120. contentctl/output/yml_output.py +0 -66
  121. contentctl-4.4.7.dist-info/RECORD +0 -173
  122. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/LICENSE.md +0 -0
  123. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,213 +1,276 @@
1
- from dataclasses import dataclass
2
- import os
3
- import glob
4
- import shutil
5
- import sys
6
- import tarfile
7
- from typing import Union
8
- from pathlib import Path
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Callable
4
+
5
+ if TYPE_CHECKING:
6
+ from contentctl.objects.baseline import Baseline
7
+ from contentctl.objects.dashboard import Dashboard
8
+ from contentctl.objects.detection import Detection
9
+ from contentctl.objects.investigation import Investigation
10
+ from contentctl.objects.lookup import Lookup
11
+ from contentctl.objects.macro import Macro
12
+ from contentctl.objects.story import Story
13
+
9
14
  import pathlib
10
- import time
11
- import timeit
12
- import datetime
13
15
  import shutil
14
- import json
15
- from contentctl.output.conf_writer import ConfWriter
16
- from contentctl.objects.enums import SecurityContentType
16
+ import tarfile
17
+
17
18
  from contentctl.objects.config import build
18
- from requests import Session, post, get
19
- from requests.auth import HTTPBasicAuth
20
19
 
21
- class ConfOutput:
22
- config: build
20
+ # These must be imported separately because they are not just used for typing,
21
+ # they are used in isinstance (which requires the object to be imported)
22
+ from contentctl.objects.lookup import FileBackedLookup, MlModel
23
+ from contentctl.output.conf_writer import ConfWriter
24
+
23
25
 
26
+ class ConfOutput:
27
+ config: build
24
28
 
25
29
  def __init__(self, config: build):
26
30
  self.config = config
27
31
 
28
- #Create the build directory if it does not exist
32
+ # Create the build directory if it does not exist
29
33
  config.getPackageDirectoryPath().parent.mkdir(parents=True, exist_ok=True)
30
-
31
- #Remove the app path, if it exists
34
+
35
+ # Remove the app path, if it exists
32
36
  shutil.rmtree(config.getPackageDirectoryPath(), ignore_errors=True)
33
-
34
- #Copy all the template files into the app
37
+
38
+ # Copy all the template files into the app
35
39
  shutil.copytree(config.getAppTemplatePath(), config.getPackageDirectoryPath())
36
-
37
40
 
38
41
  def writeHeaders(self) -> set[pathlib.Path]:
39
- written_files:set[pathlib.Path] = set()
40
- for output_app_path in ['default/analyticstories.conf',
41
- 'default/savedsearches.conf',
42
- 'default/collections.conf',
43
- 'default/es_investigations.conf',
44
- 'default/macros.conf',
45
- 'default/transforms.conf',
46
- 'default/workflow_actions.conf',
47
- 'default/app.conf',
48
- 'default/content-version.conf']:
49
- written_files.add(ConfWriter.writeConfFileHeader(pathlib.Path(output_app_path),self.config))
50
-
42
+ written_files: set[pathlib.Path] = set()
43
+ for output_app_path in [
44
+ "default/analyticstories.conf",
45
+ "default/savedsearches.conf",
46
+ "default/collections.conf",
47
+ "default/es_investigations.conf",
48
+ "default/macros.conf",
49
+ "default/transforms.conf",
50
+ "default/workflow_actions.conf",
51
+ "default/app.conf",
52
+ "default/content-version.conf",
53
+ ]:
54
+ written_files.add(
55
+ ConfWriter.writeConfFileHeader(
56
+ pathlib.Path(output_app_path), self.config
57
+ )
58
+ )
59
+
51
60
  return written_files
52
61
 
53
-
54
- #The contents of app.manifest are not a conf file, but json.
55
- #DO NOT write a header for this file type, simply create the file
56
- with open(self.config.getPackageDirectoryPath() / pathlib.Path('app.manifest'), 'w') as f:
62
+ # The contents of app.manifest are not a conf file, but json.
63
+ # DO NOT write a header for this file type, simply create the file
64
+ with open(
65
+ self.config.getPackageDirectoryPath() / pathlib.Path("app.manifest"), "w"
66
+ ):
57
67
  pass
58
-
59
-
60
-
61
-
62
- def writeMiscellaneousAppFiles(self)->set[pathlib.Path]:
63
- written_files:set[pathlib.Path] = set()
64
-
65
- written_files.add(ConfWriter.writeConfFile(pathlib.Path("default/content-version.conf"),
66
- "content-version.j2",
67
- self.config,
68
- [self.config.app]))
69
-
70
- written_files.add(ConfWriter.writeManifestFile(pathlib.Path("app.manifest"),
71
- "app.manifest.j2",
72
- self.config,
73
- [self.config.app]))
74
-
68
+
69
+ def writeMiscellaneousAppFiles(self) -> set[pathlib.Path]:
70
+ written_files: set[pathlib.Path] = set()
71
+
72
+ written_files.add(
73
+ ConfWriter.writeConfFile(
74
+ pathlib.Path("default/content-version.conf"),
75
+ "content-version.j2",
76
+ self.config,
77
+ [self.config.app],
78
+ )
79
+ )
80
+
81
+ written_files.add(
82
+ ConfWriter.writeManifestFile(
83
+ pathlib.Path("app.manifest"),
84
+ "app.manifest.j2",
85
+ self.config,
86
+ [self.config.app],
87
+ )
88
+ )
89
+
75
90
  written_files.add(ConfWriter.writeServerConf(self.config))
76
91
 
77
92
  written_files.add(ConfWriter.writeAppConf(self.config))
78
-
79
93
 
80
94
  return written_files
81
95
 
82
-
83
- def writeObjects(self, objects: list, type: SecurityContentType = None) -> set[pathlib.Path]:
84
- written_files:set[pathlib.Path] = set()
85
- if type == SecurityContentType.detections:
86
- for output_app_path, template_name in [ ('default/savedsearches.conf', 'savedsearches_detections.j2'),
87
- ('default/analyticstories.conf', 'analyticstories_detections.j2')]:
88
- written_files.add(ConfWriter.writeConfFile(pathlib.Path(output_app_path),
89
- template_name, self.config, objects))
90
-
91
- elif type == SecurityContentType.stories:
92
- written_files.add(ConfWriter.writeConfFile(pathlib.Path('default/analyticstories.conf'),
93
- 'analyticstories_stories.j2',
94
- self.config, objects))
95
-
96
- elif type == SecurityContentType.baselines:
97
- written_files.add(ConfWriter.writeConfFile(pathlib.Path('default/savedsearches.conf'),
98
- 'savedsearches_baselines.j2',
99
- self.config, objects))
100
-
101
- elif type == SecurityContentType.investigations:
102
- for output_app_path, template_name in [ ('default/savedsearches.conf', 'savedsearches_investigations.j2'),
103
- ('default/analyticstories.conf', 'analyticstories_investigations.j2')]:
104
- ConfWriter.writeConfFile(pathlib.Path(output_app_path),
105
- template_name,
106
- self.config,
107
- objects)
108
-
109
- workbench_panels = []
110
- for investigation in objects:
111
- if investigation.inputs:
112
- response_file_name_xml = investigation.lowercase_name + "___response_task.xml"
113
- workbench_panels.append(investigation)
114
- investigation.search = investigation.search.replace(">",">")
115
- investigation.search = investigation.search.replace("<","&lt;")
116
-
117
-
118
- ConfWriter.writeXmlFileHeader(pathlib.Path(f'default/data/ui/panels/workbench_panel_{response_file_name_xml}'),
119
- self.config)
120
-
121
- ConfWriter.writeXmlFile( pathlib.Path(f'default/data/ui/panels/workbench_panel_{response_file_name_xml}'),
122
- 'panel.j2',
123
- self.config,[investigation.search])
124
-
125
- for output_app_path, template_name in [ ('default/es_investigations.conf', 'es_investigations_investigations.j2'),
126
- ('default/workflow_actions.conf', 'workflow_actions.j2')]:
127
- written_files.add( ConfWriter.writeConfFile(pathlib.Path(output_app_path),
128
- template_name,
129
- self.config,
130
- workbench_panels))
131
-
132
- elif type == SecurityContentType.lookups:
133
- for output_app_path, template_name in [ ('default/collections.conf', 'collections.j2'),
134
- ('default/transforms.conf', 'transforms.j2')]:
135
- written_files.add(ConfWriter.writeConfFile(pathlib.Path(output_app_path),
136
- template_name,
137
- self.config,
138
- objects))
139
-
140
-
141
- #we want to copy all *.mlmodel files as well, not just csvs
142
- files = list(glob.iglob(str(self.config.path/ 'lookups/*.csv'))) + list(glob.iglob(str(self.config.path / 'lookups/*.mlmodel')))
143
- lookup_folder = self.config.getPackageDirectoryPath()/"lookups"
144
-
145
- # Make the new folder for the lookups
146
- # This folder almost certainly already exists because mitre_enrichment.csv has been writtent here from the app template.
147
- lookup_folder.mkdir(exist_ok=True)
148
-
149
- #Copy each lookup into the folder
150
- for lookup_name in files:
151
- lookup_path = pathlib.Path(lookup_name)
152
- if lookup_path.is_file():
153
- shutil.copy(lookup_path, lookup_folder/lookup_path.name)
154
- else:
155
- raise(Exception(f"Error copying lookup/mlmodel file. Path {lookup_path} does not exist or is not a file."))
156
-
157
- elif type == SecurityContentType.macros:
158
- written_files.add(ConfWriter.writeConfFile(pathlib.Path('default/macros.conf'),
159
- 'macros.j2',
160
- self.config, objects))
161
-
162
- elif type == SecurityContentType.dashboards:
163
- written_files.update(ConfWriter.writeDashboardFiles(self.config, objects))
96
+ def writeDetections(self, objects: list[Detection]) -> set[pathlib.Path]:
97
+ written_files: set[pathlib.Path] = set()
98
+ for output_app_path, template_name in [
99
+ ("default/savedsearches.conf", "savedsearches_detections.j2"),
100
+ ("default/analyticstories.conf", "analyticstories_detections.j2"),
101
+ ]:
102
+ written_files.add(
103
+ ConfWriter.writeConfFile(
104
+ pathlib.Path(output_app_path), template_name, self.config, objects
105
+ )
106
+ )
107
+ return written_files
164
108
 
109
+ def writeStories(self, objects: list[Story]) -> set[pathlib.Path]:
110
+ written_files: set[pathlib.Path] = set()
111
+ written_files.add(
112
+ ConfWriter.writeConfFile(
113
+ pathlib.Path("default/analyticstories.conf"),
114
+ "analyticstories_stories.j2",
115
+ self.config,
116
+ objects,
117
+ )
118
+ )
119
+ return written_files
165
120
 
121
+ def writeBaselines(self, objects: list[Baseline]) -> set[pathlib.Path]:
122
+ written_files: set[pathlib.Path] = set()
123
+ written_files.add(
124
+ ConfWriter.writeConfFile(
125
+ pathlib.Path("default/savedsearches.conf"),
126
+ "savedsearches_baselines.j2",
127
+ self.config,
128
+ objects,
129
+ )
130
+ )
166
131
  return written_files
167
-
168
132
 
133
+ def writeInvestigations(self, objects: list[Investigation]) -> set[pathlib.Path]:
134
+ written_files: set[pathlib.Path] = set()
135
+ for output_app_path, template_name in [
136
+ ("default/savedsearches.conf", "savedsearches_investigations.j2"),
137
+ ("default/analyticstories.conf", "analyticstories_investigations.j2"),
138
+ ]:
139
+ ConfWriter.writeConfFile(
140
+ pathlib.Path(output_app_path), template_name, self.config, objects
141
+ )
142
+
143
+ workbench_panels: list[Investigation] = []
144
+ for investigation in objects:
145
+ if investigation.inputs:
146
+ response_file_name_xml = (
147
+ investigation.lowercase_name + "___response_task.xml"
148
+ )
149
+ workbench_panels.append(investigation)
150
+ investigation.search = investigation.search.replace(">", "&gt;")
151
+ investigation.search = investigation.search.replace("<", "&lt;")
169
152
 
153
+ ConfWriter.writeXmlFileHeader(
154
+ pathlib.Path(
155
+ f"default/data/ui/panels/workbench_panel_{response_file_name_xml}"
156
+ ),
157
+ self.config,
158
+ )
159
+
160
+ ConfWriter.writeXmlFile(
161
+ pathlib.Path(
162
+ f"default/data/ui/panels/workbench_panel_{response_file_name_xml}"
163
+ ),
164
+ "panel.j2",
165
+ self.config,
166
+ [investigation.search],
167
+ )
168
+
169
+ for output_app_path, template_name in [
170
+ ("default/es_investigations.conf", "es_investigations_investigations.j2"),
171
+ ("default/workflow_actions.conf", "workflow_actions.j2"),
172
+ ]:
173
+ written_files.add(
174
+ ConfWriter.writeConfFile(
175
+ pathlib.Path(output_app_path),
176
+ template_name,
177
+ self.config,
178
+ workbench_panels,
179
+ )
180
+ )
181
+ return written_files
182
+
183
+ def writeLookups(self, objects: list[Lookup]) -> set[pathlib.Path]:
184
+ written_files: set[pathlib.Path] = set()
185
+ for output_app_path, template_name in [
186
+ ("default/collections.conf", "collections.j2"),
187
+ ("default/transforms.conf", "transforms.j2"),
188
+ ]:
189
+ # DO NOT write MlModels to transforms.conf. The enumeration of
190
+ # those files happens in the MLTK app by enumerating the __mlspl_*
191
+ # files in the lookups/ directory of the app
192
+ written_files.add(
193
+ ConfWriter.writeConfFile(
194
+ pathlib.Path(output_app_path),
195
+ template_name,
196
+ self.config,
197
+ [lookup for lookup in objects if not isinstance(lookup, MlModel)],
198
+ )
199
+ )
200
+
201
+ # Get the path to the lookups folder
202
+ lookup_folder = self.config.getPackageDirectoryPath() / "lookups"
203
+
204
+ # Make the new folder for the lookups
205
+ # This folder almost certainly already exists because mitre_enrichment.csv has been writtent here from the app template.
206
+ lookup_folder.mkdir(exist_ok=True)
207
+
208
+ # Copy each lookup into the folder
209
+ for lookup in objects:
210
+ # All File backed lookups, including __mlspl_ files, should be copied here,
211
+ # even though the MLModel info was intentionally not written to the
212
+ # transforms.conf file as noted above.
213
+ if isinstance(lookup, FileBackedLookup):
214
+ shutil.copy(lookup.filename, lookup_folder / lookup.app_filename.name)
215
+ return written_files
216
+
217
+ def writeMacros(self, objects: list[Macro]) -> set[pathlib.Path]:
218
+ written_files: set[pathlib.Path] = set()
219
+ written_files.add(
220
+ ConfWriter.writeConfFile(
221
+ pathlib.Path("default/macros.conf"), "macros.j2", self.config, objects
222
+ )
223
+ )
224
+ return written_files
225
+
226
+ def writeDashboards(self, objects: list[Dashboard]) -> set[pathlib.Path]:
227
+ written_files: set[pathlib.Path] = set()
228
+ written_files.update(ConfWriter.writeDashboardFiles(self.config, objects))
229
+ return written_files
170
230
 
171
-
172
231
  def packageAppTar(self) -> None:
173
-
174
- with tarfile.open(self.config.getPackageFilePath(include_version=True), "w:gz") as app_archive:
175
- app_archive.add(self.config.getPackageDirectoryPath(), arcname=self.config.getPackageDirectoryPath().name)
176
-
177
- shutil.copy2(self.config.getPackageFilePath(include_version=True),
178
- self.config.getPackageFilePath(include_version=False),
179
- follow_symlinks=False)
180
-
232
+ with tarfile.open(
233
+ self.config.getPackageFilePath(include_version=True), "w:gz"
234
+ ) as app_archive:
235
+ app_archive.add(
236
+ self.config.getPackageDirectoryPath(),
237
+ arcname=self.config.getPackageDirectoryPath().name,
238
+ )
239
+
240
+ shutil.copy2(
241
+ self.config.getPackageFilePath(include_version=True),
242
+ self.config.getPackageFilePath(include_version=False),
243
+ follow_symlinks=False,
244
+ )
245
+
181
246
  def packageAppSlim(self) -> None:
182
-
183
- raise Exception("Packaging with splunk-packaging-toolkit not currently supported as slim only supports Python 3.7. "
184
- "Please raise an issue in the contentctl GitHub if you encounter this exception.")
247
+ raise Exception(
248
+ "Packaging with splunk-packaging-toolkit not currently supported as slim only supports Python 3.7. "
249
+ "Please raise an issue in the contentctl GitHub if you encounter this exception."
250
+ )
185
251
  try:
252
+ import logging
253
+
186
254
  import slim
187
255
  from slim.utils import SlimLogger
188
- import logging
189
- #In order to avoid significant output, only emit FATAL log messages
256
+
257
+ # In order to avoid significant output, only emit FATAL log messages
190
258
  SlimLogger.set_level(logging.ERROR)
191
259
  try:
192
- slim.package(source=self.config.getPackageDirectoryPath(), output_dir=pathlib.Path(self.config.getBuildDir()))
260
+ slim.package(
261
+ source=self.config.getPackageDirectoryPath(),
262
+ output_dir=pathlib.Path(self.config.getBuildDir()),
263
+ )
193
264
  except SystemExit as e:
194
265
  raise Exception(f"Error building package with slim: {str(e)}")
195
-
196
-
266
+
197
267
  except Exception as e:
198
- print("Failed to import Splunk Packaging Toolkit (slim). slim requires Python<3.10. "
199
- "Packaging app with tar instead. This should still work, but appinspect may catch "
200
- "errors that otherwise would have been flagged by slim.")
268
+ print(
269
+ "Failed to import Splunk Packaging Toolkit (slim). slim requires Python<3.10. "
270
+ "Packaging app with tar instead. This should still work, but appinspect may catch "
271
+ "errors that otherwise would have been flagged by slim."
272
+ )
201
273
  raise Exception(f"slim (splunk packaging toolkit) not installed: {str(e)}")
202
-
203
-
204
-
205
- def packageApp(self, method=packageAppTar)->None:
206
- return method(self)
207
274
 
208
-
209
-
210
- def getElapsedTime(self, startTime:float)->datetime.timedelta:
211
- return datetime.timedelta(seconds=round(timeit.default_timer() - startTime))
212
-
213
-
275
+ def packageApp(self, method: Callable[[ConfOutput], None] = packageAppTar) -> None:
276
+ return method(self)