Skip to content

Commit 36bec98

Browse files
authored
Merge branch 'main' into release_notes_fix
2 parents 28a9b19 + 6df8352 commit 36bec98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3559
-943
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ about: Create a report to help us improve contentctl
44
title: "[BUG]"
55
labels: bug
66
assignees: ''
7+
type: "Bug"
78

89
---
910

.github/ISSUE_TEMPLATE/feature_request.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ about: Suggest an idea for this project
44
title: ''
55
labels: enhancement
66
assignees: ''
7-
7+
type: "Feature"
88
---
99

1010
**Is your feature request related to a problem? Please describe.**

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repos:
88
- id: detect-private-key
99
- id: forbid-submodules
1010
- repo: https://github.com/astral-sh/ruff-pre-commit
11-
rev: v0.9.10
11+
rev: v0.11.2
1212
hooks:
1313
- id: ruff
1414
args: [ --fix ]

contentctl/actions/build.py

Lines changed: 8 additions & 44 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 JSON_API_VERSION, 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)
@@ -114,7 +76,9 @@ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
11476
api_json_output.writeDeployments(input_dto.director_output_dto.deployments)
11577

11678
# create version file for sse api
117-
version_file = input_dto.config.getAPIPath() / "version.json"
79+
version_file = (
80+
input_dto.config.getAPIPath() / f"version_v{JSON_API_VERSION}.json"
81+
)
11882
utc_time = (
11983
datetime.datetime.now(datetime.timezone.utc)
12084
.replace(microsecond=0, tzinfo=None)

contentctl/actions/detection_testing/DetectionTestingManager.py

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,29 @@
1+
import concurrent.futures
2+
import datetime
3+
import signal
4+
import traceback
5+
from dataclasses import dataclass
16
from typing import List, Union
2-
from contentctl.objects.config import test, test_servers, Container, Infrastructure
7+
8+
import docker
9+
from pydantic import BaseModel
10+
311
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
412
DetectionTestingInfrastructure,
13+
DetectionTestingManagerOutputDto,
514
)
615
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureContainer import (
716
DetectionTestingInfrastructureContainer,
817
)
918
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import (
1019
DetectionTestingInfrastructureServer,
1120
)
12-
import signal
13-
import datetime
14-
15-
# from queue import Queue
16-
from dataclasses import dataclass
17-
18-
# import threading
19-
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
20-
DetectionTestingManagerOutputDto,
21-
)
2221
from contentctl.actions.detection_testing.views.DetectionTestingView import (
2322
DetectionTestingView,
2423
)
25-
from contentctl.objects.enums import PostTestBehavior
26-
from pydantic import BaseModel
24+
from contentctl.objects.config import Container, Infrastructure, test, test_servers
2725
from contentctl.objects.detection import Detection
28-
import concurrent.futures
29-
import docker
26+
from contentctl.objects.enums import PostTestBehavior
3027

3128

3229
@dataclass(frozen=False)
@@ -63,12 +60,14 @@ def sigint_handler(signum, frame):
6360
# a newline '\r\n' which will cause that wait to stop
6461
print("*******************************")
6562
print(
66-
"If testing is paused and you are debugging a detection, you MUST hit CTRL-D at the prompt to complete shutdown."
63+
"If testing is paused and you are debugging a detection, you MUST hit CTRL-D "
64+
"at the prompt to complete shutdown."
6765
)
6866
print("*******************************")
6967

7068
signal.signal(signal.SIGINT, sigint_handler)
7169

70+
# TODO (#337): futures can be hard to maintain/debug; let's consider alternatives
7271
with (
7372
concurrent.futures.ThreadPoolExecutor(
7473
max_workers=len(self.input_dto.config.test_instances),
@@ -80,10 +79,19 @@ def sigint_handler(signum, frame):
8079
max_workers=len(self.input_dto.config.test_instances),
8180
) as view_shutdowner,
8281
):
82+
# Capture any errors for reporting at the end after all threads have been gathered
83+
errors: dict[str, list[Exception]] = {
84+
"INSTANCE SETUP ERRORS": [],
85+
"TESTING ERRORS": [],
86+
"ERRORS DURING VIEW SHUTDOWN": [],
87+
"ERRORS DURING VIEW EXECUTION": [],
88+
}
89+
8390
# Start all the views
8491
future_views = {
8592
view_runner.submit(view.setup): view for view in self.input_dto.views
8693
}
94+
8795
# Configure all the instances
8896
future_instances_setup = {
8997
instance_pool.submit(instance.setup): instance
@@ -96,7 +104,11 @@ def sigint_handler(signum, frame):
96104
future.result()
97105
except Exception as e:
98106
self.output_dto.terminate = True
99-
print(f"Error setting up container: {str(e)}")
107+
# Output the traceback if we encounter errors in verbose mode
108+
if self.input_dto.config.verbose:
109+
tb = traceback.format_exc()
110+
print(tb)
111+
errors["INSTANCE SETUP ERRORS"].append(e)
100112

101113
# Start and wait for all tests to run
102114
if not self.output_dto.terminate:
@@ -111,7 +123,11 @@ def sigint_handler(signum, frame):
111123
future.result()
112124
except Exception as e:
113125
self.output_dto.terminate = True
114-
print(f"Error running in container: {str(e)}")
126+
# Output the traceback if we encounter errors in verbose mode
127+
if self.input_dto.config.verbose:
128+
tb = traceback.format_exc()
129+
print(tb)
130+
errors["TESTING ERRORS"].append(e)
115131

116132
self.output_dto.terminate = True
117133

@@ -123,14 +139,34 @@ def sigint_handler(signum, frame):
123139
try:
124140
future.result()
125141
except Exception as e:
126-
print(f"Error stopping view: {str(e)}")
142+
# Output the traceback if we encounter errors in verbose mode
143+
if self.input_dto.config.verbose:
144+
tb = traceback.format_exc()
145+
print(tb)
146+
errors["ERRORS DURING VIEW SHUTDOWN"].append(e)
127147

128148
# Wait for original view-related threads to complete
129149
for future in concurrent.futures.as_completed(future_views):
130150
try:
131151
future.result()
132152
except Exception as e:
133-
print(f"Error running container: {str(e)}")
153+
# Output the traceback if we encounter errors in verbose mode
154+
if self.input_dto.config.verbose:
155+
tb = traceback.format_exc()
156+
print(tb)
157+
errors["ERRORS DURING VIEW EXECUTION"].append(e)
158+
159+
# Log any errors
160+
for error_type in errors:
161+
if len(errors[error_type]) > 0:
162+
print()
163+
print(f"[{error_type}]:")
164+
for error in errors[error_type]:
165+
print(f"\t{str(error)}")
166+
if isinstance(error, ExceptionGroup):
167+
for suberror in error.exceptions: # type: ignore
168+
print(f"\t\t{str(suberror)}") # type: ignore
169+
print()
134170

135171
return self.output_dto
136172

@@ -154,12 +190,15 @@ def create_DetectionTestingInfrastructureObjects(self):
154190
)
155191
if len(parts) != 2:
156192
raise Exception(
157-
f"Expected to find a name:tag in {self.input_dto.config.container_settings.full_image_path}, "
158-
f"but instead found {parts}. Note that this path MUST include the tag, which is separated by ':'"
193+
"Expected to find a name:tag in "
194+
f"{self.input_dto.config.container_settings.full_image_path}, "
195+
f"but instead found {parts}. Note that this path MUST include the "
196+
"tag, which is separated by ':'"
159197
)
160198

161199
print(
162-
f"Getting the latest version of the container image [{self.input_dto.config.container_settings.full_image_path}]...",
200+
"Getting the latest version of the container image "
201+
f"[{self.input_dto.config.container_settings.full_image_path}]...",
163202
end="",
164203
flush=True,
165204
)
@@ -168,7 +207,8 @@ def create_DetectionTestingInfrastructureObjects(self):
168207
break
169208
except Exception as e:
170209
raise Exception(
171-
f"Failed to pull docker container image [{self.input_dto.config.container_settings.full_image_path}]: {str(e)}"
210+
"Failed to pull docker container image "
211+
f"[{self.input_dto.config.container_settings.full_image_path}]: {str(e)}"
172212
)
173213

174214
already_staged_container_files = False

contentctl/actions/detection_testing/GitService.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from contentctl.objects.config import All, Changes, Selected, test_common
1515
from contentctl.objects.data_source import DataSource
1616
from contentctl.objects.detection import Detection
17-
from contentctl.objects.lookup import CSVLookup, Lookup
17+
from contentctl.objects.lookup import CSVLookup, Lookup, RuntimeCSV
1818
from contentctl.objects.macro import Macro
1919
from contentctl.objects.security_content_object import SecurityContentObject
2020

@@ -148,6 +148,9 @@ def getChanges(self, target_branch: str) -> List[Detection]:
148148
matched = list(
149149
filter(
150150
lambda x: isinstance(x, CSVLookup)
151+
and not isinstance(
152+
x, RuntimeCSV
153+
) # RuntimeCSV is not used directly by any content
151154
and x.filename == decoded_path,
152155
self.director.lookups,
153156
)

0 commit comments

Comments
 (0)