Skip to content

Commit

Permalink
Better support for the --num_containers command line argument. Initia…
Browse files Browse the repository at this point in the history
…l support for passing the address and credentials of servers on the command line.
  • Loading branch information
pyth0n1c committed Sep 8, 2023
1 parent 32bb047 commit 2ef4d89
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 12 deletions.
57 changes: 52 additions & 5 deletions contentctl/contentctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
SecurityContentProduct,
DetectionTestingMode,
PostTestBehavior,
DetectionTestingTargetInfrastructure
)
from contentctl.input.new_content_generator import NewContentGeneratorInputDto
from contentctl.helper.config_handler import ConfigHandler

from contentctl.objects.config import Config

from contentctl.objects.app import App
from contentctl.objects.test_config import TestConfig
from contentctl.objects.test_config import TestConfig, Infrastructure
from contentctl.actions.test import Test, TestInputDto, TestOutputDto


Expand Down Expand Up @@ -128,19 +129,52 @@ def acs_deploy(args) -> None:

def test(args: argparse.Namespace):
args = configure_unattended(args)

config = start(args, read_test_file=True)

if config.test is None:
raise Exception("Error parsing test configuration. Test Object was None.")

# set some arguments that are not
# yet exposed/written properly in
# the config file
if args.mode != None:
if args.infrastructure is not None:
config.test.infrastructure_config.infrastructure_type = DetectionTestingTargetInfrastructure(args.infrastructure)
if args.mode is not None:
config.test.mode=DetectionTestingMode(args.mode)
if args.behavior != None:
if args.behavior is not None:
config.test.post_test_behavior=PostTestBehavior(args.behavior)

if args.detections_list != None:
if args.detections_list is not None:
config.test.detections_list=args.detections_list



if config.test.infrastructure_config.infrastructure_type == DetectionTestingTargetInfrastructure.container:
if args.num_containers is None:
raise Exception("Error - trying to start a test using container infrastructure but no value for --num_containers was found")
config.test.infrastructure_config.infrastructures = Infrastructure.get_infrastructure_containers(args.num_containers)
elif config.test.infrastructure_config.infrastructure_type == DetectionTestingTargetInfrastructure.server:
if args.server_info is None:
if len(config.test.infrastructure_config.infrastructures) == 0:
raise Exception("Error - trying to start a test using server infrastructure, but server information was not stored "
"in contentctl_test.yml or passed on the command line. Please see the documentation for --server_info "
"at the command line or 'infrastructures' in contentctl.yml.")
else:
print("Using server configuration from contentctl_test.yml")

else:
print("Using server configuration from command line")
config.test.infrastructure_config.infrastructures = []
for server in args.server_info:
address,username,password,hec_port,web_ui_port,api_port = server.split(":")
config.test.infrastructure_config.infrastructures.append(Infrastructure(splunk_app_username=username,
splunk_app_password=password,
instance_address=address,
hec_port=hec_port,
web_ui_port=web_ui_port,
api_port=api_port))





Expand Down Expand Up @@ -371,8 +405,21 @@ def main():
"of detections to test. Their paths should be relative to the app path.",
)


test_parser.add_argument("--unattended", action=argparse.BooleanOptionalAction)


test_parser.add_argument("--infrastructure", required=False, type=str,
choices=DetectionTestingTargetInfrastructure._member_names_, default=None,
help="Determines what infrastructure to use for testing. The options are "
"container and server. Container will set up Splunk Container(s) at runtime, "
"install all relevant apps, and perform configurations. Server will use "
"preconfigured server(s) either specified on the command line or in "
"contentctl_test.yml.")
test_parser.add_argument("--num_containers", required=False, default=1, type=int)
test_parser.add_argument("--server_info", required=False, default=None, nargs='+')


test_parser.set_defaults(func=test)

# parse them
Expand Down
42 changes: 35 additions & 7 deletions contentctl/objects/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pydantic import BaseModel, validator, root_validator, Extra, Field
from dataclasses import dataclass
from typing import Union
import re
import docker
import docker.errors

Expand Down Expand Up @@ -52,15 +53,41 @@ class Infrastructure(BaseModel, extra=Extra.forbid, validate_assignment=True):
)

instance_name: str = Field(
default="splunk_contentctl_%d",
default="Splunk_Server_Name",
title="Template to be used for naming the Splunk Test Containers or referring to Test Servers.",
)

hec_port: int = Field(default=8088, title="HTTP Event Collector Port")
web_ui_port: int = Field(default=8000, title="Web UI Port")
api_port: int = Field(default=8089, title="REST API Port")

@staticmethod
def get_infrastructure_containers(num_containers:int=1, splunk_app_username:str="admin", splunk_app_password:str="password", instance_name_template="splunk_contentctl_{index}")->list[Infrastructure]:
containers:list[Infrastructure] = []
if num_containers < 0:
raise ValueError(f"Error - you must specifiy 1 or more containers, not {num_containers}.")

#Get the starting ports
i = Infrastructure() #Instantiate to get the base port numbers

for index in range(0, num_containers):
containers.append(Infrastructure(splunk_app_username=splunk_app_username,
splunk_app_password=splunk_app_password,
instance_name=instance_name_template.format(index=index),
hec_port=i.hec_port+(index*2),
web_ui_port=i.web_ui_port+index,
api_port=i.api_port+(index*2)))


return containers

@validator("instance_name")
def validate_instance_name(cls,v,values):
if not re.fullmatch("[a-zA-Z0-9][a-zA-Z0-9_.-]*", v):
raise ValueError(f"The instance_name '{v}' is not valid. Please use an instance name which matches the regular expression '[a-zA-Z0-9][a-zA-Z0-9_.-]*'")
else:
return v

@validator("instance_address")
def validate_instance_address(cls, v, values):
try:
Expand Down Expand Up @@ -140,7 +167,7 @@ class InfrastructureConfig(BaseModel, extra=Extra.forbid, validate_assignment=Tr
default="registry.hub.docker.com/splunk/splunk:latest",
title="Full path to the container image to be used",
)
infrastructures: list[Infrastructure] = [Infrastructure()]
infrastructures: list[Infrastructure] = []


@validator("infrastructure_type", always=True)
Expand All @@ -164,7 +191,7 @@ def validate_infrastructure_type(cls, v, values):



@validator("full_image_path", always=True)
@validator("full_image_path")
def validate_full_image_path(cls, v, values):
if (
values.get("infrastructure_type", None)
Expand Down Expand Up @@ -258,7 +285,9 @@ def validate_full_image_path(cls, v, values):
@validator("infrastructures", always=True)
def validate_infrastructures(cls, v, values):
MAX_RECOMMENDED_CONTAINERS_BEFORE_WARNING = 2

if values.get("infrastructure_type",None) == DetectionTestingTargetInfrastructure.container and len(v) == 0:
v = [Infrastructure()]

if len(v) < 1:
raise (
ValueError(
Expand Down Expand Up @@ -286,7 +315,7 @@ def validate_ports_overlap(cls, v, values):
for infrastructure in v:
for k in ["hec_port", "web_ui_port", "api_port"]:
if getattr(infrastructure, k) in ports:
raise ValueError(f"Port {infrastructure.get(k)} used more than once in container infrastructure ports")
raise ValueError(f"Port {getattr(infrastructure, k)} used more than once in container infrastructure ports")
ports.add(getattr(infrastructure, k))
return v

Expand Down Expand Up @@ -321,7 +350,6 @@ def validate_repo_path(cls,v):
if ALWAYS_PULL_REPO:
r.remotes.origin.pull()
except Exception as e:
print("exception 3")
raise ValueError(f"Error pulling git repository {v}: {str(e)}")
print("repo path looks good")
return v
Expand Down

0 comments on commit 2ef4d89

Please sign in to comment.