Skip to content

Commit 7e5d21d

Browse files
authored
Merge pull request #355 from splunk/d_assistant_take_2
D assistant take 2
2 parents a5b2817 + 11cd116 commit 7e5d21d

28 files changed

Lines changed: 1276 additions & 366 deletions

contentctl/actions/build.py

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1+
import datetime
2+
import json
3+
import pathlib
14
import shutil
2-
35
from dataclasses import dataclass
46

57
from contentctl.input.director import DirectorOutputDto
8+
from contentctl.objects.config import build
9+
from contentctl.output.api_json_output import ApiJsonOutput
610
from contentctl.output.conf_output import ConfOutput
711
from contentctl.output.conf_writer import ConfWriter
8-
from contentctl.output.api_json_output import ApiJsonOutput
9-
from contentctl.output.data_source_writer import DataSourceWriter
10-
from contentctl.objects.lookup import CSVLookup, Lookup_Type
11-
import pathlib
12-
import json
13-
import datetime
14-
import uuid
15-
16-
from contentctl.objects.config import build
1712

1813

1914
@dataclass(frozen=True)
@@ -28,39 +23,6 @@ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
2823
updated_conf_files: set[pathlib.Path] = set()
2924
conf_output = ConfOutput(input_dto.config)
3025

31-
# Construct a path to a YML that does not actually exist.
32-
# We mock this "fake" path since the YML does not exist.
33-
# This ensures the checking for the existence of the CSV is correct
34-
data_sources_fake_yml_path = (
35-
input_dto.config.getPackageDirectoryPath()
36-
/ "lookups"
37-
/ "data_sources.yml"
38-
)
39-
40-
# Construct a special lookup whose CSV is created at runtime and
41-
# written directly into the lookups folder. We will delete this after a build,
42-
# assuming that it is successful.
43-
data_sources_lookup_csv_path = (
44-
input_dto.config.getPackageDirectoryPath()
45-
/ "lookups"
46-
/ "data_sources.csv"
47-
)
48-
49-
DataSourceWriter.writeDataSourceCsv(
50-
input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path
51-
)
52-
input_dto.director_output_dto.addContentToDictMappings(
53-
CSVLookup.model_construct(
54-
name="data_sources",
55-
id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
56-
version=1,
57-
author=input_dto.config.app.author_name,
58-
date=datetime.date.today(),
59-
description="A lookup file that will contain the data source objects for detections.",
60-
lookup_type=Lookup_Type.csv,
61-
file_path=data_sources_fake_yml_path,
62-
)
63-
)
6426
updated_conf_files.update(conf_output.writeHeaders())
6527
updated_conf_files.update(
6628
conf_output.writeLookups(input_dto.director_output_dto.lookups)

contentctl/actions/detection_testing/views/DetectionTestingView.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
from pydantic import BaseModel
66

7-
from contentctl.objects.config import test_common
8-
97
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
108
DetectionTestingManagerOutputDto,
119
)
1210
from contentctl.helper.utils import Utils
13-
from contentctl.objects.enums import DetectionStatus
1411
from contentctl.objects.base_test_result import TestResultStatus
12+
from contentctl.objects.config import test_common
13+
from contentctl.objects.enums import ContentStatus
1514

1615

1716
class DetectionTestingView(BaseModel, abc.ABC):
@@ -117,11 +116,11 @@ def getSummaryObject(
117116
total_skipped += 1
118117

119118
# Aggregate production status metrics
120-
if detection.status == DetectionStatus.production:
119+
if detection.status == ContentStatus.production:
121120
total_production += 1
122-
elif detection.status == DetectionStatus.experimental:
121+
elif detection.status == ContentStatus.experimental:
123122
total_experimental += 1
124-
elif detection.status == DetectionStatus.deprecated:
123+
elif detection.status == ContentStatus.deprecated:
125124
total_deprecated += 1
126125

127126
# Check if the detection is manual_test

contentctl/actions/initialize.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
import shutil
21
import os
32
import pathlib
3+
import shutil
4+
5+
from contentctl.objects.baseline import Baseline
46
from contentctl.objects.config import test
7+
from contentctl.objects.dashboard import Dashboard
8+
from contentctl.objects.data_source import DataSource
9+
from contentctl.objects.deployment import Deployment
10+
from contentctl.objects.detection import Detection
11+
from contentctl.objects.investigation import Investigation
12+
from contentctl.objects.lookup import Lookup
13+
from contentctl.objects.macro import Macro
14+
from contentctl.objects.playbook import Playbook
15+
from contentctl.objects.removed_security_content_object import (
16+
RemovedSecurityContentObject,
17+
)
18+
from contentctl.objects.story import Story
519
from contentctl.output.yml_writer import YmlWriter
620

721

@@ -13,21 +27,33 @@ def execute(self, config: test) -> None:
1327

1428
YmlWriter.writeYmlFile(str(config.path / "contentctl.yml"), config.model_dump())
1529

16-
# Create the following empty directories:
30+
# Create the following empty directories. Each type of content,
31+
# even if you don't have any of that type of content, need its own directory to exist.
32+
for contentType in [
33+
Detection,
34+
Playbook,
35+
Story,
36+
DataSource,
37+
Investigation,
38+
Macro,
39+
Lookup,
40+
Dashboard,
41+
Baseline,
42+
Deployment,
43+
RemovedSecurityContentObject,
44+
]:
45+
contentType.containing_folder().mkdir(exist_ok=False, parents=True)
46+
47+
# Some other directories that do not map directly to a piece of content also must exist
48+
1749
for emptyDir in [
18-
"lookups",
19-
"baselines",
20-
"data_sources",
2150
"docs",
2251
"reporting",
23-
"investigations",
2452
"detections/application",
2553
"detections/cloud",
2654
"detections/endpoint",
2755
"detections/network",
2856
"detections/web",
29-
"macros",
30-
"stories",
3157
]:
3258
# Throw an error if this directory already exists
3359
(config.path / emptyDir).mkdir(exist_ok=False, parents=True)
@@ -60,7 +86,7 @@ def execute(self, config: test) -> None:
6086
source_directory = pathlib.Path(os.path.dirname(__file__)) / templateDir
6187
target_directory = config.path / targetDir
6288
# Throw an exception if the target exists
63-
shutil.copytree(source_directory, target_directory, dirs_exist_ok=False)
89+
shutil.copytree(source_directory, target_directory, dirs_exist_ok=True)
6490

6591
# Create a README.md file. Note that this is the README.md for the repository, not the
6692
# one which will actually be packaged into the app. That is located in the app_template folder.

contentctl/actions/validate.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import pathlib
22

3-
from contentctl.input.director import Director, DirectorOutputDto
4-
from contentctl.objects.config import validate
53
from contentctl.enrichments.attack_enrichment import AttackEnrichment
64
from contentctl.enrichments.cve_enrichment import CveEnrichment
7-
from contentctl.objects.atomic import AtomicEnrichment
8-
from contentctl.objects.lookup import FileBackedLookup
5+
from contentctl.helper.splunk_app import SplunkApp
96
from contentctl.helper.utils import Utils
7+
from contentctl.input.director import Director, DirectorOutputDto
8+
from contentctl.objects.atomic import AtomicEnrichment
9+
from contentctl.objects.config import validate
1010
from contentctl.objects.data_source import DataSource
11-
from contentctl.helper.splunk_app import SplunkApp
11+
from contentctl.objects.lookup import FileBackedLookup, RuntimeCSV
1212

1313

1414
class Validate:
@@ -17,16 +17,6 @@ def execute(self, input_dto: validate) -> DirectorOutputDto:
1717
AtomicEnrichment.getAtomicEnrichment(input_dto),
1818
AttackEnrichment.getAttackEnrichment(input_dto),
1919
CveEnrichment.getCveEnrichment(input_dto),
20-
[],
21-
[],
22-
[],
23-
[],
24-
[],
25-
[],
26-
[],
27-
[],
28-
[],
29-
[],
3020
)
3121

3222
director = Director(director_output_dto)
@@ -68,7 +58,10 @@ def ensure_no_orphaned_files_in_lookups(
6858
usedLookupFiles: list[pathlib.Path] = [
6959
lookup.filename
7060
for lookup in director_output_dto.lookups
61+
# Of course Runtime CSVs do not have underlying CSV files, so make
62+
# sure that we do not check for that existence.
7163
if isinstance(lookup, FileBackedLookup)
64+
and not isinstance(lookup, RuntimeCSV)
7265
] + [
7366
lookup.file_path
7467
for lookup in director_output_dto.lookups

contentctl/helper/utils.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,34 @@
1-
import os
2-
import git
3-
import shutil
4-
import requests
1+
import logging
2+
import pathlib
53
import random
4+
import shutil
65
import string
6+
from math import ceil
77
from timeit import default_timer
8-
import pathlib
9-
import logging
8+
from typing import TYPE_CHECKING, Tuple, Union
109

11-
from typing import Union, Tuple
10+
import git
11+
import requests
1212
import tqdm
13-
from math import ceil
14-
15-
from typing import TYPE_CHECKING
1613

1714
if TYPE_CHECKING:
1815
from contentctl.objects.security_content_object import SecurityContentObject
1916
from contentctl.objects.security_content_object import SecurityContentObject
2017

21-
2218
TOTAL_BYTES = 0
2319
ALWAYS_PULL = True
2420

2521

2622
class Utils:
2723
@staticmethod
28-
def get_all_yml_files_from_directory(path: str) -> list[pathlib.Path]:
29-
listOfFiles: list[pathlib.Path] = []
30-
base_path = pathlib.Path(path)
31-
if not base_path.exists():
32-
return listOfFiles
33-
for dirpath, dirnames, filenames in os.walk(path):
34-
for file in filenames:
35-
if file.endswith(".yml"):
36-
listOfFiles.append(pathlib.Path(os.path.join(dirpath, file)))
24+
def get_all_yml_files_from_directory(path: pathlib.Path) -> list[pathlib.Path]:
25+
if not path.exists():
26+
raise FileNotFoundError(
27+
f"Trying to find files in the directory '{path.absolute()}', but it does not exist.\n"
28+
"It is not mandatory to have content/YMLs in this directory, but it must exist. Please create it."
29+
)
3730

38-
return sorted(listOfFiles)
31+
return sorted(pathlib.Path(yml_path) for yml_path in path.glob("**/*.yml"))
3932

4033
@staticmethod
4134
def get_security_content_files_from_directory(

0 commit comments

Comments
 (0)