contentctl 5.2.0__py3-none-any.whl → 5.3.1__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/actions/build.py +5 -43
- contentctl/actions/detection_testing/DetectionTestingManager.py +64 -24
- contentctl/actions/detection_testing/GitService.py +4 -1
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +146 -42
- contentctl/actions/detection_testing/views/DetectionTestingView.py +5 -6
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +2 -0
- contentctl/actions/initialize.py +35 -9
- contentctl/actions/release_notes.py +14 -12
- contentctl/actions/test.py +16 -20
- contentctl/actions/validate.py +9 -16
- contentctl/helper/utils.py +69 -20
- contentctl/input/director.py +147 -119
- contentctl/input/yml_reader.py +39 -27
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +95 -21
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +548 -8
- contentctl/objects/baseline.py +24 -6
- contentctl/objects/config.py +32 -8
- contentctl/objects/content_versioning_service.py +508 -0
- contentctl/objects/correlation_search.py +53 -63
- contentctl/objects/dashboard.py +15 -1
- contentctl/objects/data_source.py +13 -1
- contentctl/objects/deployment.py +23 -9
- contentctl/objects/detection.py +2 -0
- contentctl/objects/enums.py +28 -18
- contentctl/objects/investigation.py +40 -20
- contentctl/objects/lookup.py +62 -6
- contentctl/objects/macro.py +19 -4
- contentctl/objects/playbook.py +16 -2
- contentctl/objects/rba.py +1 -33
- contentctl/objects/removed_security_content_object.py +50 -0
- contentctl/objects/security_content_object.py +1 -0
- contentctl/objects/story.py +37 -5
- contentctl/output/api_json_output.py +5 -3
- contentctl/output/conf_output.py +9 -1
- contentctl/output/runtime_csv_writer.py +111 -0
- contentctl/output/svg_output.py +4 -5
- contentctl/output/templates/savedsearches_detections.j2 +2 -6
- {contentctl-5.2.0.dist-info → contentctl-5.3.1.dist-info}/METADATA +4 -3
- {contentctl-5.2.0.dist-info → contentctl-5.3.1.dist-info}/RECORD +42 -40
- {contentctl-5.2.0.dist-info → contentctl-5.3.1.dist-info}/WHEEL +1 -1
- contentctl/output/data_source_writer.py +0 -52
- {contentctl-5.2.0.dist-info → contentctl-5.3.1.dist-info}/LICENSE.md +0 -0
- {contentctl-5.2.0.dist-info → contentctl-5.3.1.dist-info}/entry_points.txt +0 -0
contentctl/objects/rba.py
CHANGED
|
@@ -4,9 +4,7 @@ from abc import ABC
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Annotated, Set
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel, Field,
|
|
8
|
-
|
|
9
|
-
from contentctl.objects.enums import RiskSeverity
|
|
7
|
+
from pydantic import BaseModel, Field, model_serializer
|
|
10
8
|
|
|
11
9
|
RiskScoreValue_Type = Annotated[int, Field(ge=1, le=100)]
|
|
12
10
|
|
|
@@ -108,36 +106,6 @@ class RBAObject(BaseModel, ABC):
|
|
|
108
106
|
risk_objects: Annotated[Set[RiskObject], Field(min_length=1)]
|
|
109
107
|
threat_objects: Set[ThreatObject]
|
|
110
108
|
|
|
111
|
-
@computed_field
|
|
112
|
-
@property
|
|
113
|
-
def risk_score(self) -> RiskScoreValue_Type:
|
|
114
|
-
# First get the maximum score associated with
|
|
115
|
-
# a risk object. If there are no objects, then
|
|
116
|
-
# we should throw an exception.
|
|
117
|
-
if len(self.risk_objects) == 0:
|
|
118
|
-
raise Exception(
|
|
119
|
-
"There must be at least one Risk Object present to get Severity."
|
|
120
|
-
)
|
|
121
|
-
return max([risk_object.score for risk_object in self.risk_objects])
|
|
122
|
-
|
|
123
|
-
@computed_field
|
|
124
|
-
@property
|
|
125
|
-
def severity(self) -> RiskSeverity:
|
|
126
|
-
if 0 <= self.risk_score <= 20:
|
|
127
|
-
return RiskSeverity.INFORMATIONAL
|
|
128
|
-
elif 20 < self.risk_score <= 40:
|
|
129
|
-
return RiskSeverity.LOW
|
|
130
|
-
elif 40 < self.risk_score <= 60:
|
|
131
|
-
return RiskSeverity.MEDIUM
|
|
132
|
-
elif 60 < self.risk_score <= 80:
|
|
133
|
-
return RiskSeverity.HIGH
|
|
134
|
-
elif 80 < self.risk_score <= 100:
|
|
135
|
-
return RiskSeverity.CRITICAL
|
|
136
|
-
else:
|
|
137
|
-
raise Exception(
|
|
138
|
-
f"Error getting severity - risk_score must be between 0-100, but was actually {self.risk_score}"
|
|
139
|
-
)
|
|
140
|
-
|
|
141
109
|
@model_serializer
|
|
142
110
|
def serialize_rba(self) -> dict[str, str | list[dict[str, str | int]]]:
|
|
143
111
|
return {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
|
|
6
|
+
from pydantic import ConfigDict, HttpUrl, computed_field, field_validator
|
|
7
|
+
|
|
8
|
+
from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
|
|
9
|
+
SecurityContentObject_Abstract,
|
|
10
|
+
)
|
|
11
|
+
from contentctl.objects.enums import ContentStatus
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SecurityContentObject(SecurityContentObject_Abstract):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RemovedSecurityContentObject(SecurityContentObject):
|
|
19
|
+
# We MUST allow extra fields here because the python definitions of the underlying
|
|
20
|
+
# objects can change. We do not want to throw pasing errors on any of these, but we will
|
|
21
|
+
# only expose fields that are defined in the SecurityContentObject definiton directly
|
|
22
|
+
model_config = ConfigDict(validate_default=True, extra="ignore")
|
|
23
|
+
status: ContentStatus
|
|
24
|
+
|
|
25
|
+
@field_validator("status", mode="after")
|
|
26
|
+
@classmethod
|
|
27
|
+
def NarrowStatus(cls, status: ContentStatus) -> ContentStatus:
|
|
28
|
+
return cls.NarrowStatusTemplate(status, [ContentStatus.removed])
|
|
29
|
+
|
|
30
|
+
@computed_field
|
|
31
|
+
@cached_property
|
|
32
|
+
def migration_guide(self) -> HttpUrl:
|
|
33
|
+
"""
|
|
34
|
+
A link to the research site containing a migration guide for the content
|
|
35
|
+
|
|
36
|
+
:returns: URL to the research site
|
|
37
|
+
:rtype: HTTPUrl
|
|
38
|
+
"""
|
|
39
|
+
# this is split up so that we can explicilty ignore the warning on constructing
|
|
40
|
+
# the HttpUrl but catch other type issues
|
|
41
|
+
# link = f"https://research.splunk.com/migration_guide/{self.id}"
|
|
42
|
+
# This can likely be dynamically generated per detection, but for now we
|
|
43
|
+
# just make it static
|
|
44
|
+
link = "https://research.splunk.com/migration_guide/"
|
|
45
|
+
|
|
46
|
+
return HttpUrl(url=link) # type: ignore
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def containing_folder(cls) -> pathlib.Path:
|
|
50
|
+
return pathlib.Path("removed")
|
contentctl/objects/story.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING, List
|
|
6
|
+
|
|
7
|
+
from pydantic import (
|
|
8
|
+
Field,
|
|
9
|
+
HttpUrl,
|
|
10
|
+
computed_field,
|
|
11
|
+
field_validator,
|
|
12
|
+
model_serializer,
|
|
13
|
+
model_validator,
|
|
14
|
+
)
|
|
7
15
|
|
|
8
16
|
from contentctl.objects.story_tags import StoryTags
|
|
9
17
|
|
|
@@ -14,20 +22,40 @@ if TYPE_CHECKING:
|
|
|
14
22
|
from contentctl.objects.detection import Detection
|
|
15
23
|
from contentctl.objects.investigation import Investigation
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
import pathlib
|
|
26
|
+
|
|
27
|
+
from contentctl.objects.enums import ContentStatus
|
|
18
28
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class Story(SecurityContentObject):
|
|
22
32
|
narrative: str = Field(...)
|
|
23
33
|
tags: StoryTags = Field(...)
|
|
24
|
-
status:
|
|
34
|
+
status: ContentStatus
|
|
25
35
|
# These are updated when detection and investigation objects are created.
|
|
26
36
|
# Specifically in the model_post_init functions
|
|
27
37
|
detections: List[Detection] = []
|
|
28
38
|
investigations: List[Investigation] = []
|
|
29
39
|
baselines: List[Baseline] = []
|
|
30
40
|
|
|
41
|
+
@field_validator("status", mode="after")
|
|
42
|
+
@classmethod
|
|
43
|
+
def NarrowStatus(cls, status: ContentStatus) -> ContentStatus:
|
|
44
|
+
return cls.NarrowStatusTemplate(
|
|
45
|
+
status, [ContentStatus.production, ContentStatus.deprecated]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def containing_folder(cls) -> pathlib.Path:
|
|
50
|
+
return pathlib.Path("stories")
|
|
51
|
+
|
|
52
|
+
@computed_field
|
|
53
|
+
@cached_property
|
|
54
|
+
def researchSiteLink(self) -> HttpUrl:
|
|
55
|
+
return HttpUrl(
|
|
56
|
+
url=f"https://research.splunk.com/stories/{self.name.lower().replace(' ', '_')}"
|
|
57
|
+
) # type:ignore
|
|
58
|
+
|
|
31
59
|
@computed_field
|
|
32
60
|
@property
|
|
33
61
|
def data_sources(self) -> list[DataSource]:
|
|
@@ -145,3 +173,7 @@ class Story(SecurityContentObject):
|
|
|
145
173
|
@property
|
|
146
174
|
def baseline_names(self) -> List[str]:
|
|
147
175
|
return [baseline.name for baseline in self.baselines]
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def static_get_conf_stanza_name(cls, name: str, app: CustomApp) -> str:
|
|
179
|
+
return name
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from typing import TYPE_CHECKING
|
|
3
4
|
|
|
4
5
|
if TYPE_CHECKING:
|
|
6
|
+
from contentctl.objects.baseline import Baseline
|
|
7
|
+
from contentctl.objects.deployment import Deployment
|
|
5
8
|
from contentctl.objects.detection import Detection
|
|
9
|
+
from contentctl.objects.investigation import Investigation
|
|
6
10
|
from contentctl.objects.lookup import Lookup
|
|
7
11
|
from contentctl.objects.macro import Macro
|
|
8
12
|
from contentctl.objects.story import Story
|
|
9
|
-
from contentctl.objects.baseline import Baseline
|
|
10
|
-
from contentctl.objects.investigation import Investigation
|
|
11
|
-
from contentctl.objects.deployment import Deployment
|
|
12
13
|
|
|
13
14
|
import os
|
|
14
15
|
import pathlib
|
|
@@ -42,6 +43,7 @@ class ApiJsonOutput:
|
|
|
42
43
|
"search",
|
|
43
44
|
"how_to_implement",
|
|
44
45
|
"known_false_positives",
|
|
46
|
+
"rba",
|
|
45
47
|
"references",
|
|
46
48
|
"datamodel",
|
|
47
49
|
"macros",
|
contentctl/output/conf_output.py
CHANGED
|
@@ -93,6 +93,10 @@ class ConfOutput:
|
|
|
93
93
|
|
|
94
94
|
return written_files
|
|
95
95
|
|
|
96
|
+
# TODO (#339): we could have a discrepancy between detections tested and those delivered
|
|
97
|
+
# based on the jinja2 template
|
|
98
|
+
# {% if (detection.type == 'TTP' or detection.type == 'Anomaly' or
|
|
99
|
+
# detection.type == 'Hunting' or detection.type == 'Correlation') %}
|
|
96
100
|
def writeDetections(self, objects: list[Detection]) -> set[pathlib.Path]:
|
|
97
101
|
written_files: set[pathlib.Path] = set()
|
|
98
102
|
for output_app_path, template_name in [
|
|
@@ -211,7 +215,11 @@ class ConfOutput:
|
|
|
211
215
|
# even though the MLModel info was intentionally not written to the
|
|
212
216
|
# transforms.conf file as noted above.
|
|
213
217
|
if isinstance(lookup, FileBackedLookup):
|
|
214
|
-
|
|
218
|
+
with (
|
|
219
|
+
open(lookup_folder / lookup.app_filename.name, "w") as output_file,
|
|
220
|
+
lookup.content_file_handle as output,
|
|
221
|
+
):
|
|
222
|
+
output_file.write(output.read())
|
|
215
223
|
return written_files
|
|
216
224
|
|
|
217
225
|
def writeMacros(self, objects: list[Macro]) -> set[pathlib.Path]:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from typing import TYPE_CHECKING, List
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from contentctl.input.director import DirectorOutputDto
|
|
9
|
+
|
|
10
|
+
from contentctl.objects.config import CustomApp
|
|
11
|
+
from contentctl.objects.data_source import DataSource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RuntimeCsvWriter:
|
|
15
|
+
@staticmethod
|
|
16
|
+
def generateDeprecationCSVContent(
|
|
17
|
+
director: DirectorOutputDto, app: CustomApp
|
|
18
|
+
) -> str:
|
|
19
|
+
with StringIO() as output_buffer:
|
|
20
|
+
fieldNames = [
|
|
21
|
+
"Name",
|
|
22
|
+
"Content Type",
|
|
23
|
+
"Removed in Version",
|
|
24
|
+
"Reason",
|
|
25
|
+
"Replacement Content",
|
|
26
|
+
"Replacement Content Link",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
writer = csv.DictWriter(output_buffer, fieldnames=fieldNames)
|
|
30
|
+
writer.writeheader()
|
|
31
|
+
|
|
32
|
+
for content in director.name_to_content_map.values():
|
|
33
|
+
if content.deprecation_info is not None:
|
|
34
|
+
try:
|
|
35
|
+
writer.writerow(
|
|
36
|
+
{
|
|
37
|
+
"Name": content.deprecation_info.contentType.static_get_conf_stanza_name(
|
|
38
|
+
content.name, app
|
|
39
|
+
),
|
|
40
|
+
"Content Type": content.deprecation_info.contentType.__name__,
|
|
41
|
+
"Removed in Version": content.deprecation_info.removed_in_version,
|
|
42
|
+
"Reason": content.deprecation_info.reason,
|
|
43
|
+
"Replacement Content": "\n".join(
|
|
44
|
+
[
|
|
45
|
+
c.name
|
|
46
|
+
for c in content.deprecation_info.replacement_content
|
|
47
|
+
]
|
|
48
|
+
)
|
|
49
|
+
or "No Replacement Content Available",
|
|
50
|
+
"Replacement Content Link": "\n".join(
|
|
51
|
+
[
|
|
52
|
+
str(c.researchSiteLink)
|
|
53
|
+
for c in content.deprecation_info.replacement_content
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
or "No Content Link Available",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(e)
|
|
61
|
+
import code
|
|
62
|
+
|
|
63
|
+
code.interact(local=locals())
|
|
64
|
+
return output_buffer.getvalue()
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def generateDatasourceCSVContent(
|
|
68
|
+
data_source_objects: List[DataSource],
|
|
69
|
+
) -> str:
|
|
70
|
+
with StringIO() as output_buffer:
|
|
71
|
+
writer = csv.writer(output_buffer)
|
|
72
|
+
# Write the header
|
|
73
|
+
writer.writerow(
|
|
74
|
+
[
|
|
75
|
+
"name",
|
|
76
|
+
"id",
|
|
77
|
+
"author",
|
|
78
|
+
"source",
|
|
79
|
+
"sourcetype",
|
|
80
|
+
"separator",
|
|
81
|
+
"supported_TA_name",
|
|
82
|
+
"supported_TA_version",
|
|
83
|
+
"supported_TA_url",
|
|
84
|
+
"description",
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
# Write the data
|
|
88
|
+
for data_source in data_source_objects:
|
|
89
|
+
if len(data_source.supported_TA) > 0:
|
|
90
|
+
supported_TA_name = data_source.supported_TA[0].name
|
|
91
|
+
supported_TA_version = data_source.supported_TA[0].version
|
|
92
|
+
supported_TA_url = data_source.supported_TA[0].url or ""
|
|
93
|
+
else:
|
|
94
|
+
supported_TA_name = ""
|
|
95
|
+
supported_TA_version = ""
|
|
96
|
+
supported_TA_url = ""
|
|
97
|
+
writer.writerow(
|
|
98
|
+
[
|
|
99
|
+
data_source.name,
|
|
100
|
+
data_source.id,
|
|
101
|
+
data_source.author,
|
|
102
|
+
data_source.source,
|
|
103
|
+
data_source.sourcetype,
|
|
104
|
+
data_source.separator,
|
|
105
|
+
supported_TA_name,
|
|
106
|
+
supported_TA_version,
|
|
107
|
+
supported_TA_url,
|
|
108
|
+
data_source.description,
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
return output_buffer.getvalue()
|
contentctl/output/svg_output.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import pathlib
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, List
|
|
3
3
|
|
|
4
|
-
from contentctl.objects.enums import SecurityContentType
|
|
5
|
-
from contentctl.output.jinja_writer import JinjaWriter
|
|
6
|
-
from contentctl.objects.enums import DetectionStatus
|
|
7
4
|
from contentctl.objects.detection import Detection
|
|
5
|
+
from contentctl.objects.enums import ContentStatus, SecurityContentType
|
|
6
|
+
from contentctl.output.jinja_writer import JinjaWriter
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class SvgOutput:
|
|
@@ -49,7 +48,7 @@ class SvgOutput:
|
|
|
49
48
|
[
|
|
50
49
|
detection
|
|
51
50
|
for detection in detections
|
|
52
|
-
if detection.status ==
|
|
51
|
+
if detection.status == ContentStatus.production
|
|
53
52
|
],
|
|
54
53
|
)
|
|
55
54
|
# deprecated_dict = self.get_badge_dict("Deprecated", detections, [detection for detection in detections if detection.status == DetectionStatus.deprecated])
|
|
@@ -65,14 +65,10 @@ action.notable.param.nes_fields = {{ detection.nes_fields }}
|
|
|
65
65
|
action.notable.param.rule_description = {{ detection.deployment.alert_action.notable.rule_description | custom_jinja2_enrichment_filter(detection) | escapeNewlines()}}
|
|
66
66
|
action.notable.param.rule_title = {% if detection.type | lower == "correlation" %}RBA: {{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% else %}{{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% endif +%}
|
|
67
67
|
action.notable.param.security_domain = {{ detection.tags.security_domain }}
|
|
68
|
-
{
|
|
69
|
-
action.notable.param.severity = {{ detection.rba.severity }}
|
|
70
|
-
{% else %}
|
|
71
|
-
{# Correlations do not have detection.rba defined, but should get a default severity #}
|
|
72
|
-
action.notable.param.severity = high
|
|
73
|
-
{% endif %}
|
|
68
|
+
action.notable.param.severity = {{ detection.severity }}
|
|
74
69
|
{% endif %}
|
|
75
70
|
{% if detection.deployment.alert_action.email %}
|
|
71
|
+
action.email = 1
|
|
76
72
|
action.email.subject.alert = {{ detection.deployment.alert_action.email.subject | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }}
|
|
77
73
|
action.email.to = {{ detection.deployment.alert_action.email.to }}
|
|
78
74
|
action.email.message.alert = {{ detection.deployment.alert_action.email.message | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: contentctl
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.3.1
|
|
4
4
|
Summary: Splunk Content Control Tool
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
Author: STRT
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
|
|
15
15
|
Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
|
|
16
|
-
Requires-Dist: attackcti (>=0.4
|
|
16
|
+
Requires-Dist: attackcti (>=0.5.4,<0.6)
|
|
17
17
|
Requires-Dist: bottle (>=0.12.25,<0.14.0)
|
|
18
18
|
Requires-Dist: docker (>=7.1.0,<8.0.0)
|
|
19
19
|
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
|
@@ -22,8 +22,9 @@ Requires-Dist: pydantic (>=2.9.2,<2.10.0)
|
|
|
22
22
|
Requires-Dist: pygit2 (>=1.15.1,<2.0.0)
|
|
23
23
|
Requires-Dist: questionary (>=2.0.1,<3.0.0)
|
|
24
24
|
Requires-Dist: requests (>=2.32.3,<2.33.0)
|
|
25
|
+
Requires-Dist: rich (>=14.0.0,<15.0.0)
|
|
25
26
|
Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
|
|
26
|
-
Requires-Dist: setuptools (>=69.5.1,<
|
|
27
|
+
Requires-Dist: setuptools (>=69.5.1,<79.0.0)
|
|
27
28
|
Requires-Dist: splunk-sdk (>=2.0.2,<3.0.0)
|
|
28
29
|
Requires-Dist: tqdm (>=4.66.5,<5.0.0)
|
|
29
30
|
Requires-Dist: tyro (>=0.9.2,<0.10.0)
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
contentctl/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
contentctl/actions/build.py,sha256=
|
|
2
|
+
contentctl/actions/build.py,sha256=agpHT6QCCFdWTs4jU7v8xqvGy3DG_zgsZZT96j1gcW0,3965
|
|
3
3
|
contentctl/actions/deploy_acs.py,sha256=w3OqO8GXzB_5zHrE8lDYbadAy4Etw7F2o84Gze74RY0,3264
|
|
4
|
-
contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=
|
|
5
|
-
contentctl/actions/detection_testing/GitService.py,sha256=
|
|
4
|
+
contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=94apBwLkXWsgdLSvE9f_KqCfQSdmDChMncMcsEdY1A8,10974
|
|
5
|
+
contentctl/actions/detection_testing/GitService.py,sha256=HU1fKkb5463weqSZ3LrTVHtNrzBH_f5pE99-zD2j1A8,11345
|
|
6
6
|
contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=bGUVKjKv96lTw1GZ4Kw1JX-Yicu4aOJWm-IL524e9HI,2302
|
|
7
|
-
contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=
|
|
7
|
+
contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=nJVo_L3Y4V0Uk7VCGHY58waGCBKcfujIFmxKC83oVgY,61082
|
|
8
8
|
contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=qYWgRW7uc-15jzwv5xSUF2xyLDmtyGyMfuXkQK9j-aM,7221
|
|
9
9
|
contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py,sha256=Q1ZfCYOp54O39bgTScZMInkmZiU-bGAM9Hiwr2mq5ms,370
|
|
10
10
|
contentctl/actions/detection_testing/progress_bar.py,sha256=UrpNCqxTmQ4hfoRZgxPJ1xvDVwMrTq0UnotdryHN0gM,3232
|
|
11
|
-
contentctl/actions/detection_testing/views/DetectionTestingView.py,sha256=
|
|
12
|
-
contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py,sha256
|
|
11
|
+
contentctl/actions/detection_testing/views/DetectionTestingView.py,sha256=gSrLNR8Nz-YYsXunWLRn53UH7TeQugDKjnDLXy4cL6M,7466
|
|
12
|
+
contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py,sha256=kvMP4G2tLUZPM6AFHO6KrlkjWBNaHmEoke5qxS7XBws,2238
|
|
13
13
|
contentctl/actions/detection_testing/views/DetectionTestingViewFile.py,sha256=G-6YqBtj0R1A9eiPrgHP2yvUMm7H8wopTBYjxIEIl8g,1090
|
|
14
14
|
contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py,sha256=CXV1fByf3J-Jc4D9U6jgWSaUhNzjcMpvEgRMuusF2vU,4740
|
|
15
15
|
contentctl/actions/doc_gen.py,sha256=P2-RYsJoW-QuhAkSpOQespDLJBC-4Cq3-XGTmadK8Ys,936
|
|
16
|
-
contentctl/actions/initialize.py,sha256=
|
|
16
|
+
contentctl/actions/initialize.py,sha256=KXVUyjLMS7yE34wd2odyj5pVXyc_eOlvH_d7LzgR_Bc,4238
|
|
17
17
|
contentctl/actions/inspect.py,sha256=zFNbDXY7Bi1xTBHirNyHpH1-2A1n3rsOsRvu8E0xUao,19375
|
|
18
18
|
contentctl/actions/new_content.py,sha256=xs0QvHzlrf0g-EgdUJTkdDdFaA-uEGmzMTixDt6NcTY,8212
|
|
19
|
-
contentctl/actions/release_notes.py,sha256=
|
|
19
|
+
contentctl/actions/release_notes.py,sha256=rrloomsLBfl53xpjqDez6RgHU5AE4Gb9ASrivGbYYVs,17122
|
|
20
20
|
contentctl/actions/reporting.py,sha256=GF32i7sHdc47bw-VWSW-nZ1QBaUl6Ni1JjV5_SOyiAU,1660
|
|
21
|
-
contentctl/actions/test.py,sha256=
|
|
22
|
-
contentctl/actions/validate.py,sha256=
|
|
21
|
+
contentctl/actions/test.py,sha256=ftZazqoqv7bLNhyW23aRnDpetG9zltS8wr4Xq9Hls0k,6268
|
|
22
|
+
contentctl/actions/validate.py,sha256=hgcczMFA8xOo4T--RviBPJdZ7INfkq5kCZkWFB7rnDs,5879
|
|
23
23
|
contentctl/api.py,sha256=6s17vNOW1E1EzQqOCXAa5uWuhwwShu-JkGSgrsOFEMs,6329
|
|
24
24
|
contentctl/contentctl.py,sha256=nR8nHxXY0elvQogVHFqsyid7Ch5sMnIiNAOFbCa0yzI,11755
|
|
25
25
|
contentctl/enrichments/attack_enrichment.py,sha256=68C9xQ8Q3YX-luRdK2hLnwWtRFpheFA2kE4v5GOLGEo,6358
|
|
@@ -28,58 +28,60 @@ contentctl/enrichments/splunk_app_enrichment.py,sha256=Xynxjjkqlw0_RtQ1thGSFwy1I
|
|
|
28
28
|
contentctl/helper/link_validator.py,sha256=kzEi2GdncPWSi-UKNerXm2jtTJfFQ5goS9pqyAz5U5c,7427
|
|
29
29
|
contentctl/helper/logger.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
contentctl/helper/splunk_app.py,sha256=Zq_C9rjNVqCjBNgm-5CWdBpXyeX5jSpbE-QTGptEZlk,14571
|
|
31
|
-
contentctl/helper/utils.py,sha256=
|
|
32
|
-
contentctl/input/director.py,sha256=
|
|
31
|
+
contentctl/helper/utils.py,sha256=1_6cbvvbPXWxym3ZhRhL18ttmXLXiHbavpXAkROtGcg,21154
|
|
32
|
+
contentctl/input/director.py,sha256=rThzfssOG4v52ClhVwUx-sU0MKWb_UaMy3MSANCrwmo,11999
|
|
33
33
|
contentctl/input/new_content_questions.py,sha256=z2C4Mg7-EyxtiF2z9m4SnSbi6QO4CUPB3wg__JeMXIQ,4067
|
|
34
|
-
contentctl/input/yml_reader.py,sha256=
|
|
35
|
-
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=
|
|
36
|
-
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=
|
|
34
|
+
contentctl/input/yml_reader.py,sha256=L27b14_xXQYypUV1eAzZrfMtwtkzAZx--6nRo3RNZnE,2729
|
|
35
|
+
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=Gsb6v44VpWeGnMi79viJ0zO81pZ9GuiaqQJyHM-ba5I,46861
|
|
36
|
+
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=VzAkq-oJ05Y7Y2BzoUG8De8tVANV5X9ZvHbrDe65iRA,33282
|
|
37
37
|
contentctl/objects/alert_action.py,sha256=iEvdEOT4TrTXT0z4rQ_W5v79hPJpPhFPSzo7TuHDxwA,1376
|
|
38
38
|
contentctl/objects/annotated_types.py,sha256=xR4EKvdOpNDEt0doGs8XjxCzKK99J2NHZgHFAmt7p2c,424
|
|
39
39
|
contentctl/objects/atomic.py,sha256=5nl-JhZnymadi8B8ZEJ8l80DnpvjG-OlRxUjVKR6ffY,7341
|
|
40
40
|
contentctl/objects/base_test.py,sha256=JG6qlr7xe9P71n3CzKOro8_bsmDQGYDfTG9YooHQSIE,1105
|
|
41
41
|
contentctl/objects/base_test_result.py,sha256=TYYzTPKWqp9rHTebWoid50uxAp_iALZouril4sFwIcA,5197
|
|
42
|
-
contentctl/objects/baseline.py,sha256=
|
|
42
|
+
contentctl/objects/baseline.py,sha256=EMcuz_9sVgOFh3YCj871GSAA6v3FIkRTf90-LAHq-J0,3700
|
|
43
43
|
contentctl/objects/baseline_tags.py,sha256=Eomy8y3HV-E6Lym5B5ZZTtsmQJYi6Jd4y8GZpTWGYgQ,1643
|
|
44
|
-
contentctl/objects/config.py,sha256=
|
|
44
|
+
contentctl/objects/config.py,sha256=8F_zpcnFyE_rSdGomm2qeVDG6tTpjhrVyK6BsNHM8js,49357
|
|
45
45
|
contentctl/objects/constants.py,sha256=VwwQtJBGC_zb3ukjb3A7P0CwAlyhacWiXczwAW5Jiog,5466
|
|
46
|
-
contentctl/objects/
|
|
47
|
-
contentctl/objects/
|
|
48
|
-
contentctl/objects/
|
|
49
|
-
contentctl/objects/
|
|
46
|
+
contentctl/objects/content_versioning_service.py,sha256=BDk_TV1PTVoXpPcUxqTLa5_bjkfOs9PFYgqTuzOS9UI,20566
|
|
47
|
+
contentctl/objects/correlation_search.py,sha256=Zui6GAtYSUnMUzJkKOQ5sSYDTYoAxODUICpHXwxNMwo,45147
|
|
48
|
+
contentctl/objects/dashboard.py,sha256=owp-bYVagmSHUpVyOHgGtAEaKFSHAXN7kDhYqs09H_g,4998
|
|
49
|
+
contentctl/objects/data_source.py,sha256=O58GArXVlflz3dCtVOn96Ubyi5_ekSC1N9LuveQNws4,2019
|
|
50
|
+
contentctl/objects/deployment.py,sha256=OctNayxFPRvrQtTklAKgfjCXFKOspD19swLj0hi6dWE,3323
|
|
50
51
|
contentctl/objects/deployment_email.py,sha256=_Sdr_BNjvXECiFonRHLkiOrIQp3slnUaERbptqRbD0Q,206
|
|
51
52
|
contentctl/objects/deployment_notable.py,sha256=j5AniTRDcw32El5H91qKOXDVZvUYxnIuM4Zzlhrm9cM,258
|
|
52
53
|
contentctl/objects/deployment_phantom.py,sha256=Qs9UH3pYe2M3evLLgn5FblTe28QH1QojVaBGM_Ydvjw,261
|
|
53
54
|
contentctl/objects/deployment_rba.py,sha256=n_v79NhcLYHyABceKsI_iEziWhp3uNrqxIUrC8tdjD4,184
|
|
54
55
|
contentctl/objects/deployment_scheduling.py,sha256=PbyAeIEV6ShHuwfzF4LtGrv6tNt1cwNdl-VDQLj0rE8,257
|
|
55
56
|
contentctl/objects/deployment_slack.py,sha256=pC8-BB4qOD5fUqUi7Oj2Tre7-kKVqW2xEvCF7tZENQ4,194
|
|
56
|
-
contentctl/objects/detection.py,sha256=
|
|
57
|
+
contentctl/objects/detection.py,sha256=i-BCxTrP11fmxHLI1zv3XESF4R-f9BScl6FFOdUlHxY,641
|
|
57
58
|
contentctl/objects/detection_metadata.py,sha256=JMz8rtcn5HfeEoaAx34kw2wXa35qsRIap_mXoY0Vbss,2237
|
|
58
59
|
contentctl/objects/detection_stanza.py,sha256=-BRQNib5NNhY7Z2fILS5xkpjNkGSLF7qBciTmgOgLV8,3112
|
|
59
60
|
contentctl/objects/detection_tags.py,sha256=j92t4TWlNNVdFi4_DoHvEyvJuURlBp5_o1xv2w2pAVk,10699
|
|
60
61
|
contentctl/objects/drilldown.py,sha256=Vinw6UYlOl0YzoRA_0oBCfHA5Gvgu5p-rEsfBIgMCdI,4186
|
|
61
|
-
contentctl/objects/enums.py,sha256=
|
|
62
|
+
contentctl/objects/enums.py,sha256=nWufu5YgzllBfDQBneIe_Hf_erNXouERciqU_di5DNo,13754
|
|
62
63
|
contentctl/objects/errors.py,sha256=xX_FDUaJbJiOWgjgrzjtYW5QsD41UZ2KWqH-yGkHaCU,5554
|
|
63
64
|
contentctl/objects/integration_test.py,sha256=TYjKyH4YinUnYXOse5BQGCa4-ez_5mtoMwvh1JJcb0o,1254
|
|
64
65
|
contentctl/objects/integration_test_result.py,sha256=_uUSgqgjFhEZM8UwOJI6Q9K-ekIrbKU6OPdqHZycl-s,279
|
|
65
|
-
contentctl/objects/investigation.py,sha256=
|
|
66
|
+
contentctl/objects/investigation.py,sha256=GZsvhSZO7ZSmhg2ZeT-kPMqDG-GYpTXIvGBgV1H2lwQ,4030
|
|
66
67
|
contentctl/objects/investigation_tags.py,sha256=qDGNusrWDvCX_GcBEzag2MydSV0LIhGxoXZGgxDXfHA,1317
|
|
67
|
-
contentctl/objects/lookup.py,sha256=
|
|
68
|
-
contentctl/objects/macro.py,sha256=
|
|
68
|
+
contentctl/objects/lookup.py,sha256=u0s2kxIFtQV4TJdUyG0wp4EdEF_D4w71Axmpz1-MXPo,15627
|
|
69
|
+
contentctl/objects/macro.py,sha256=cMQ_frqJnX2NSy-21cbUnMRZZxBionQ2KOxuC9J9bp0,4051
|
|
69
70
|
contentctl/objects/manual_test.py,sha256=cx_XAtQ8VG8Ui_F553Xnut75vFEOtRwm1dDIIWNpOaM,952
|
|
70
71
|
contentctl/objects/manual_test_result.py,sha256=FyCVVf-f1DKs-qBkM4tbKfY6mkrW25NcIEBqyaDC2rE,156
|
|
71
72
|
contentctl/objects/mitre_attack_enrichment.py,sha256=PCakRksW5qrTENIZ7JirEZplE9xpmvSvX2GKv7N8j_k,3683
|
|
72
73
|
contentctl/objects/notable_action.py,sha256=sW5XlpGznMHqyBmGXtXrl22hWLiCoKkfGCasGtK3rGo,1607
|
|
73
74
|
contentctl/objects/notable_event.py,sha256=2aOtmfnsdInTtN_fHAGIKmBTBritjHbS_Nc-pqL-GbY,689
|
|
74
|
-
contentctl/objects/playbook.py,sha256=
|
|
75
|
+
contentctl/objects/playbook.py,sha256=veG2luPfFrOMdzl99D8gsO85HYSJ8kZMYWj3GG64HKk,2879
|
|
75
76
|
contentctl/objects/playbook_tags.py,sha256=O5obkQyb82YdJEii8ZJEQtrHtLOSnAvAkT1qIgpCK2s,1547
|
|
76
|
-
contentctl/objects/rba.py,sha256=
|
|
77
|
+
contentctl/objects/rba.py,sha256=2xE_DXhQvG6tVLJTXYaFEBm9owePE4QG0NVgdcVgoiY,3547
|
|
78
|
+
contentctl/objects/removed_security_content_object.py,sha256=bx-gVCqzT81E5jKncMD3-yKawTnl3tWsuzRBmsAqeqQ,1852
|
|
77
79
|
contentctl/objects/risk_analysis_action.py,sha256=v-TQktXEEzbGzmTtqwEykXoSKdGnIlK_JojnqvvAE1s,4370
|
|
78
80
|
contentctl/objects/risk_event.py,sha256=JQUmXriiwi5FetqVnhM0hf5cUp6LzLSNPuoecC2JKK0,12593
|
|
79
81
|
contentctl/objects/risk_object.py,sha256=5iUKW_UwQLjjLWiD_vlE78uwH9bkaMNCHRNmKM25W1Q,905
|
|
80
82
|
contentctl/objects/savedsearches_conf.py,sha256=Dn_Pxd9i3RT6DwNh6JrgmfxjsO3q15xzMksYr3wIGwQ,8624
|
|
81
|
-
contentctl/objects/security_content_object.py,sha256=
|
|
82
|
-
contentctl/objects/story.py,sha256=
|
|
83
|
+
contentctl/objects/security_content_object.py,sha256=2mEf-wt3hMsLEyo4yatyK66jKjgUOVjJHIN9fgQB5nA,246
|
|
84
|
+
contentctl/objects/story.py,sha256=1JCiF9D1EZeVcoMXXDoWkOqHXQn4TsQgl8EtUN59a2E,5796
|
|
83
85
|
contentctl/objects/story_tags.py,sha256=IYumFuBF2Bt7HtW4lBfCRo2EUpjMYlnNjpx24jBErs4,2365
|
|
84
86
|
contentctl/objects/test_attack_data.py,sha256=7p-kOJguTZtG9y5th5U3qfPFvpiAWLST_OBw8dwWl_4,488
|
|
85
87
|
contentctl/objects/test_group.py,sha256=r-dXyddok4yslv8SIjwOpqylbN1rdjsRi-HIijvpWD0,2602
|
|
@@ -88,16 +90,16 @@ contentctl/objects/throttling.py,sha256=oupWmdtvwAXzLmD3MBJyAU18SD2L2ciEZWUcnL8M
|
|
|
88
90
|
contentctl/objects/unit_test.py,sha256=-rtSmZ8N2UZ4NkDsfzNXzXiF6dTDwt_jsQ_14xp0hjs,1005
|
|
89
91
|
contentctl/objects/unit_test_baseline.py,sha256=ezg8Ctih_3che2ln2tuVCAtRPHaf5tDMR3dGb34MqaA,287
|
|
90
92
|
contentctl/objects/unit_test_result.py,sha256=gqHqYN5XGBKdV-mdKhAdwfOw4_PpN3i9z_b6ciByDSc,2928
|
|
91
|
-
contentctl/output/api_json_output.py,sha256=
|
|
93
|
+
contentctl/output/api_json_output.py,sha256=s07bD79qFyFQIzfsTpIoFVyGYWlC77J9DNAlVwZwaOs,8269
|
|
92
94
|
contentctl/output/attack_nav_output.py,sha256=cbQNZkcNCKaQm7Ht70_tcmTvixtsuVDjQB4BpZ8s-Ts,2489
|
|
93
95
|
contentctl/output/attack_nav_writer.py,sha256=AiQU3q8hzz_lJECI-sjyqOsWx64HUugg3aAHEeZl-qM,2750
|
|
94
|
-
contentctl/output/conf_output.py,sha256=
|
|
96
|
+
contentctl/output/conf_output.py,sha256=OJ4u38FEkuBHvIiK_57HoxUJasWV2rkX2abVamKh3Qg,11045
|
|
95
97
|
contentctl/output/conf_writer.py,sha256=9eqt2tm1xjs397pwWLz5oPJcMHbs62ejRG7KghGQQCI,15137
|
|
96
|
-
contentctl/output/data_source_writer.py,sha256=hjr0b5zfJ2UHcDLbCkmTrqma1ngu8F5vWFPJEwOZwU8,1860
|
|
97
98
|
contentctl/output/doc_md_output.py,sha256=wlgbzBD2hUbQNIW2zv5sdrq2UdAKhOZJUYSObnpWQfY,3552
|
|
98
99
|
contentctl/output/jinja_writer.py,sha256=5PbFrc8KuLWrlNIHDvMTyvJ18u_mtjd5Led6-9sn2Eo,1204
|
|
99
100
|
contentctl/output/json_writer.py,sha256=waw73wOmalSrUFcr2K1CWR-xz5oW8il10zDAn56mtMg,1041
|
|
100
|
-
contentctl/output/
|
|
101
|
+
contentctl/output/runtime_csv_writer.py,sha256=ZlK3kDg6YwfR4Sj4xgJIhTHZ_GDVfDnQDHS0PqAnpt0,4336
|
|
102
|
+
contentctl/output/svg_output.py,sha256=8KClwfUIoLXnns-jYFzvv5OMs0-uwTFI0oZw9DWjTg4,2948
|
|
101
103
|
contentctl/output/templates/analyticstories_detections.j2,sha256=6ZiQO8np6KkX8skVoIB0BN9_s8SBW3qeo8IBA8r8GQk,923
|
|
102
104
|
contentctl/output/templates/analyticstories_investigations.j2,sha256=kqy9lR6W3avqETCM2tSZ8WWOlfiyOtFv6G5N4SZWSaQ,527
|
|
103
105
|
contentctl/output/templates/analyticstories_stories.j2,sha256=MxkmwsgW1oge2YJhbgAzXVcTplSr5JjKIDxX4SBZV0E,676
|
|
@@ -121,7 +123,7 @@ contentctl/output/templates/header.j2,sha256=3usV7jm1q6J-QNnQrZzII9cN0XEGQjg_eVK
|
|
|
121
123
|
contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaBLX2Dge7Y,390
|
|
122
124
|
contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
|
|
123
125
|
contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
|
|
124
|
-
contentctl/output/templates/savedsearches_detections.j2,sha256=
|
|
126
|
+
contentctl/output/templates/savedsearches_detections.j2,sha256=mbOSSDoHwTfzpxQRVz03FvFNQJ26lWqcUtl6uW6tUZY,5874
|
|
125
127
|
contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
|
|
126
128
|
contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
|
|
127
129
|
contentctl/output/templates/transforms.j2,sha256=TEKZi8DWpcCysRTNvuLEgAwx-g1SZ2E0CkLiu6v6AlU,1339
|
|
@@ -161,8 +163,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
161
163
|
contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
|
|
162
164
|
contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
|
|
163
165
|
contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
|
|
164
|
-
contentctl-5.
|
|
165
|
-
contentctl-5.
|
|
166
|
-
contentctl-5.
|
|
167
|
-
contentctl-5.
|
|
168
|
-
contentctl-5.
|
|
166
|
+
contentctl-5.3.1.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
|
|
167
|
+
contentctl-5.3.1.dist-info/METADATA,sha256=1nKu_O4jpY0Vtv0lNteAbgJR_Fx8Wc5UhTal5frFyTg,5134
|
|
168
|
+
contentctl-5.3.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
169
|
+
contentctl-5.3.1.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
|
|
170
|
+
contentctl-5.3.1.dist-info/RECORD,,
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import csv
|
|
2
|
-
from contentctl.objects.data_source import DataSource
|
|
3
|
-
from typing import List
|
|
4
|
-
import pathlib
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class DataSourceWriter:
|
|
8
|
-
@staticmethod
|
|
9
|
-
def writeDataSourceCsv(
|
|
10
|
-
data_source_objects: List[DataSource], file_path: pathlib.Path
|
|
11
|
-
):
|
|
12
|
-
with open(file_path, mode="w", newline="") as file:
|
|
13
|
-
writer = csv.writer(file)
|
|
14
|
-
# Write the header
|
|
15
|
-
writer.writerow(
|
|
16
|
-
[
|
|
17
|
-
"name",
|
|
18
|
-
"id",
|
|
19
|
-
"author",
|
|
20
|
-
"source",
|
|
21
|
-
"sourcetype",
|
|
22
|
-
"separator",
|
|
23
|
-
"supported_TA_name",
|
|
24
|
-
"supported_TA_version",
|
|
25
|
-
"supported_TA_url",
|
|
26
|
-
"description",
|
|
27
|
-
]
|
|
28
|
-
)
|
|
29
|
-
# Write the data
|
|
30
|
-
for data_source in data_source_objects:
|
|
31
|
-
if len(data_source.supported_TA) > 0:
|
|
32
|
-
supported_TA_name = data_source.supported_TA[0].name
|
|
33
|
-
supported_TA_version = data_source.supported_TA[0].version
|
|
34
|
-
supported_TA_url = data_source.supported_TA[0].url or ""
|
|
35
|
-
else:
|
|
36
|
-
supported_TA_name = ""
|
|
37
|
-
supported_TA_version = ""
|
|
38
|
-
supported_TA_url = ""
|
|
39
|
-
writer.writerow(
|
|
40
|
-
[
|
|
41
|
-
data_source.name,
|
|
42
|
-
data_source.id,
|
|
43
|
-
data_source.author,
|
|
44
|
-
data_source.source,
|
|
45
|
-
data_source.sourcetype,
|
|
46
|
-
data_source.separator,
|
|
47
|
-
supported_TA_name,
|
|
48
|
-
supported_TA_version,
|
|
49
|
-
supported_TA_url,
|
|
50
|
-
data_source.description,
|
|
51
|
-
]
|
|
52
|
-
)
|
|
File without changes
|
|
File without changes
|