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.
- contentctl/__init__.py +1 -1
- contentctl/actions/build.py +102 -57
- contentctl/actions/deploy_acs.py +29 -24
- contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
- contentctl/actions/detection_testing/GitService.py +134 -76
- contentctl/actions/detection_testing/generate_detection_coverage_badge.py +48 -30
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
- contentctl/actions/detection_testing/progress_bar.py +9 -6
- contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
- contentctl/actions/doc_gen.py +9 -5
- contentctl/actions/initialize.py +45 -33
- contentctl/actions/inspect.py +118 -61
- contentctl/actions/new_content.py +155 -108
- contentctl/actions/release_notes.py +276 -146
- contentctl/actions/reporting.py +23 -19
- contentctl/actions/test.py +33 -28
- contentctl/actions/validate.py +55 -34
- contentctl/api.py +54 -45
- contentctl/contentctl.py +124 -90
- contentctl/enrichments/attack_enrichment.py +112 -72
- contentctl/enrichments/cve_enrichment.py +34 -28
- contentctl/enrichments/splunk_app_enrichment.py +38 -36
- contentctl/helper/link_validator.py +101 -78
- contentctl/helper/splunk_app.py +69 -41
- contentctl/helper/utils.py +58 -53
- contentctl/input/director.py +68 -36
- contentctl/input/new_content_questions.py +27 -35
- contentctl/input/yml_reader.py +28 -18
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
- contentctl/objects/alert_action.py +10 -9
- contentctl/objects/annotated_types.py +1 -1
- contentctl/objects/atomic.py +65 -54
- contentctl/objects/base_test.py +5 -3
- contentctl/objects/base_test_result.py +19 -11
- contentctl/objects/baseline.py +62 -30
- contentctl/objects/baseline_tags.py +30 -24
- contentctl/objects/config.py +790 -597
- contentctl/objects/constants.py +33 -56
- contentctl/objects/correlation_search.py +150 -136
- contentctl/objects/dashboard.py +55 -41
- contentctl/objects/data_source.py +16 -17
- contentctl/objects/deployment.py +43 -44
- contentctl/objects/deployment_email.py +3 -2
- contentctl/objects/deployment_notable.py +4 -2
- contentctl/objects/deployment_phantom.py +7 -6
- contentctl/objects/deployment_rba.py +3 -2
- contentctl/objects/deployment_scheduling.py +3 -2
- contentctl/objects/deployment_slack.py +3 -2
- contentctl/objects/detection.py +5 -2
- contentctl/objects/detection_metadata.py +1 -0
- contentctl/objects/detection_stanza.py +7 -2
- contentctl/objects/detection_tags.py +58 -103
- contentctl/objects/drilldown.py +66 -34
- contentctl/objects/enums.py +81 -100
- contentctl/objects/errors.py +16 -24
- contentctl/objects/integration_test.py +3 -3
- contentctl/objects/integration_test_result.py +1 -0
- contentctl/objects/investigation.py +59 -36
- contentctl/objects/investigation_tags.py +30 -19
- contentctl/objects/lookup.py +304 -101
- contentctl/objects/macro.py +55 -39
- contentctl/objects/manual_test.py +3 -3
- contentctl/objects/manual_test_result.py +1 -0
- contentctl/objects/mitre_attack_enrichment.py +17 -16
- contentctl/objects/notable_action.py +2 -1
- contentctl/objects/notable_event.py +1 -3
- contentctl/objects/playbook.py +37 -35
- contentctl/objects/playbook_tags.py +23 -13
- contentctl/objects/rba.py +96 -0
- contentctl/objects/risk_analysis_action.py +15 -11
- contentctl/objects/risk_event.py +110 -160
- contentctl/objects/risk_object.py +1 -0
- contentctl/objects/savedsearches_conf.py +9 -7
- contentctl/objects/security_content_object.py +5 -2
- contentctl/objects/story.py +54 -49
- contentctl/objects/story_tags.py +56 -45
- contentctl/objects/test_attack_data.py +2 -1
- contentctl/objects/test_group.py +5 -2
- contentctl/objects/threat_object.py +1 -0
- contentctl/objects/throttling.py +27 -18
- contentctl/objects/unit_test.py +3 -4
- contentctl/objects/unit_test_baseline.py +5 -5
- contentctl/objects/unit_test_result.py +6 -6
- contentctl/output/api_json_output.py +233 -220
- contentctl/output/attack_nav_output.py +21 -21
- contentctl/output/attack_nav_writer.py +29 -37
- contentctl/output/conf_output.py +235 -172
- contentctl/output/conf_writer.py +201 -125
- contentctl/output/data_source_writer.py +38 -26
- contentctl/output/doc_md_output.py +53 -27
- contentctl/output/jinja_writer.py +19 -15
- contentctl/output/json_writer.py +21 -11
- contentctl/output/svg_output.py +56 -38
- contentctl/output/templates/analyticstories_detections.j2 +2 -2
- contentctl/output/templates/analyticstories_stories.j2 +1 -1
- contentctl/output/templates/collections.j2 +1 -1
- contentctl/output/templates/doc_detections.j2 +0 -5
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/savedsearches_baselines.j2 +2 -2
- contentctl/output/templates/savedsearches_detections.j2 +10 -11
- contentctl/output/templates/savedsearches_investigations.j2 +2 -2
- contentctl/output/templates/transforms.j2 +6 -8
- contentctl/output/yml_writer.py +29 -20
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
- contentctl/templates/stories/cobalt_strike.yml +1 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/METADATA +5 -4
- contentctl-5.0.0.dist-info/RECORD +168 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/WHEEL +1 -1
- contentctl/actions/initialize_old.py +0 -245
- contentctl/objects/event_source.py +0 -11
- contentctl/objects/observable.py +0 -37
- contentctl/output/detection_writer.py +0 -28
- contentctl/output/new_content_yml_output.py +0 -56
- contentctl/output/yml_output.py +0 -66
- contentctl-4.4.7.dist-info/RECORD +0 -173
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/LICENSE.md +0 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/entry_points.txt +0 -0
contentctl/output/conf_output.py
CHANGED
|
@@ -1,213 +1,276 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
from
|
|
8
|
-
from
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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 [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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("<","<")
|
|
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(">", ">")
|
|
151
|
+
investigation.search = investigation.search.replace("<", "<")
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
199
|
-
|
|
200
|
-
|
|
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)
|