Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup(decl): cleanup test command and introduce process builder #246

Merged
merged 6 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 104 additions & 85 deletions cmd/declarative/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/falcosecurity/event-generator/pkg/alert/retriever/grpcretriever"
containerbuilder "github.com/falcosecurity/event-generator/pkg/container/builder"
"github.com/falcosecurity/event-generator/pkg/label"
processbuilder "github.com/falcosecurity/event-generator/pkg/process/builder"
"github.com/falcosecurity/event-generator/pkg/test"
testbuilder "github.com/falcosecurity/event-generator/pkg/test/builder"
"github.com/falcosecurity/event-generator/pkg/test/loader"
Expand Down Expand Up @@ -175,71 +176,39 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
os.Exit(1)
}

// Retrieve the already populated test ID. The test ID absence is used to uniquely identify the root process in the
// process chain.
testID := cw.TestID
isRootProcess := testID == ""

labels, err := label.ParseSet(cw.Labels)
if err != nil {
logger.Error(err, "Error parsing labels")
exitAndCancel()
}

logger = enrichLogger(logger, labels)
logger = enrichLoggerWithLabels(logger, labels)

description, err := loadTestsDescription(logger, cw.TestsDescriptionFile, cw.TestsDescription)
if err != nil {
logger.Error(err, "Error loading tests description")
exitAndCancel()
}

resourceBuilder, err := resbuilder.New()
if err != nil {
logger.Error(err, "Error creating resource builder")
exitAndCancel()
}

syscallBuilder := sysbuilder.New()
stepBuilder, err := stepbuilder.New(syscallBuilder)
if err != nil {
logger.Error(err, "Error creating step builder")
exitAndCancel()
}

testBuilder, err := testbuilder.New(resourceBuilder, stepBuilder)
if err != nil {
logger.Error(err, "Error creating test builder")
exitAndCancel()
}

containerBuilderOptions := []containerbuilder.Option{
containerbuilder.WithUnixSocketPath(cw.ContainerRuntimeUnixSocketPath),
containerbuilder.WithBaseImageName(cw.ContainerBaseImageName),
containerbuilder.WithBaseImagePullPolicy(cw.ContainerImagePullPolicy),
}
containerBuilder, err := containerbuilder.New(containerBuilderOptions...)
if err != nil {
logger.Error(err, "Error creating container builder")
exitAndCancel()
}

runnerBuilder, err := runnerbuilder.New(testBuilder, containerBuilder)
runnerBuilder, err := cw.createRunnerBuilder()
if err != nil {
logger.Error(err, "Error creating runner builder")
exitAndCancel()
}

// Retrieve the already populated test ID. The test ID absence is used to uniquely identify the root process in the
// process chain.
testID := cw.TestID
isRootProcess := testID == ""

// Initialize tester and Falco alerts collection.
var testr tester.Tester
testerWaitGroup := sync.WaitGroup{}
if isRootProcess && !cw.skipOutcomeVerification {
if testr, err = cw.initTester(logger); err != nil {
logger.Error(err, "Error initializing tester")
if testr, err = cw.createTester(logger); err != nil {
logger.Error(err, "Error creating tester")
exitAndCancel()
}
}

testerWaitGroup := sync.WaitGroup{}
if testr != nil {
go func() {
if err := testr.StartAlertsCollection(ctx); err != nil {
logger.Error(err, "Error starting tester execution")
Expand All @@ -248,8 +217,7 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
}()
}

// Prepare parameters shared by runners.
runnerLogger := logger.WithName("runner")
// Prepare parameters shared by all runners.
runnerEnviron := cw.buildRunnerEnviron(cmd)
var runnerLabels *label.Set
if labels != nil {
Expand All @@ -272,29 +240,21 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
if isRootProcess {
// Generate a new UID for the test.
testUID = uuid.New()
testID = fmt.Sprintf("%s%s", testIDIgnorePrefix, testUID.String())

// Ensure the process chain has at least one element. If the user didn't specify anything, add a default
// process to the chain.
if testDesc.Context == nil {
testDesc.Context = &loader.TestContext{}
}
if len(testDesc.Context.Processes) == 0 && testDesc.Context.Container == nil {
testDesc.Context.Processes = []loader.ProcessContext{{}}
}
testID = newTestID(&testUID)
ensureProcessChainLeaf(testDesc)
} else {
// Extract UID from test ID.
var err error
testUID, err = uuid.Parse(strings.TrimPrefix(testID, testIDIgnorePrefix))
testUID, err = extractTestUID(testID)
if err != nil {
logger.Error(err, "Error parsing test UID", "testUid")
logger.Error(err, "Error extracting test UID from test ID", "testId", testID)
exitAndCancel()
}
}

logger = logger.WithValues("testUid", testUID)
runnerLogger := runnerLogger.WithValues("testName", testDesc.Name, "testIndex", testIndex, "testUid", testUID)

runnerLogger := logger.WithName("runner")
runnerDescription := &runner.Description{
Environ: runnerEnviron,
TestDescriptionEnvKey: cw.DescriptionEnvKey,
Expand All @@ -311,30 +271,8 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
}

logger.Info("Starting test execution...")

if err := runnerInstance.Run(ctx, testID, testIndex, testDesc); err != nil {
var resBuildErr *test.ResourceBuildError
var stepBuildErr *test.StepBuildError
var resCreationErr *test.ResourceCreationError
var stepRunErr *test.StepBuildError

switch {
case errors.As(err, &resBuildErr):
logger.Error(resBuildErr.Err, "Error building test resource", "resourceName", resBuildErr.ResourceName,
"resourceIndex", resBuildErr.ResourceIndex)
case errors.As(err, &stepBuildErr):
logger.Error(stepBuildErr.Err, "Error building test step", "stepName", stepBuildErr.StepName,
"stepIndex", stepBuildErr.StepIndex)
case errors.As(err, &resCreationErr):
logger.Error(resCreationErr.Err, "Error creating test resource", "resourceName",
resCreationErr.ResourceName, "resourceIndex", resCreationErr.ResourceIndex)
case errors.As(err, &stepRunErr):
logger.Error(stepRunErr.Err, "Error running test step", "stepName", stepRunErr.StepName, "stepIndex",
stepRunErr.StepIndex)
default:
logger.Error(err, "Error running test")
}

logRunnerError(logger, err)
exitAndCancel()
}

Expand All @@ -348,9 +286,9 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) {
testerWaitGroup.Wait()
}

// enrichLogger creates a new logger, starting from the provided one, with the information extracted from the provided
// labels.
func enrichLogger(logger logr.Logger, labels *label.Set) logr.Logger {
// enrichLoggerWithLabels creates a new logger, starting from the provided one, with the information extracted from the
// provided labels.
func enrichLoggerWithLabels(logger logr.Logger, labels *label.Set) logr.Logger {
if labels == nil {
return logger.WithName("root")
}
Expand Down Expand Up @@ -400,7 +338,42 @@ func loadTestsDescription(logger logr.Logger, descriptionFilePath, description s
return ldr.Load(os.Stdin)
}

func (cw *CommandWrapper) initTester(logger logr.Logger) (tester.Tester, error) {
// createRunnerBuilder creates a new runner builder.
func (cw *CommandWrapper) createRunnerBuilder() (runner.Builder, error) {
resourceProcessBuilder := processbuilder.New()
resourceBuilder, err := resbuilder.New(resourceProcessBuilder)
if err != nil {
return nil, fmt.Errorf("error creating resource builder: %w", err)
}

syscallBuilder := sysbuilder.New()
stepBuilder, err := stepbuilder.New(syscallBuilder)
if err != nil {
return nil, fmt.Errorf("error creating step builder: %w", err)
}

testBuilder, err := testbuilder.New(resourceBuilder, stepBuilder)
if err != nil {
return nil, fmt.Errorf("error creating test builder: %w", err)
}

runnerProcessBuilder := processbuilder.New()

runnerContainerBuilderOptions := []containerbuilder.Option{
containerbuilder.WithUnixSocketPath(cw.ContainerRuntimeUnixSocketPath),
containerbuilder.WithBaseImageName(cw.ContainerBaseImageName),
containerbuilder.WithBaseImagePullPolicy(cw.ContainerImagePullPolicy),
}
runnerContainerBuilder, err := containerbuilder.New(runnerContainerBuilderOptions...)
if err != nil {
return nil, fmt.Errorf("error creating container builder: %w", err)
}

return runnerbuilder.New(testBuilder, runnerProcessBuilder, runnerContainerBuilder)
}

// createTester creates a new tester.
func (cw *CommandWrapper) createTester(logger logr.Logger) (tester.Tester, error) {
gRPCRetrieverOptions := []grpcretriever.Option{
grpcretriever.WithUnixSocketPath(cw.unixSocketPath),
grpcretriever.WithHostname(cw.hostname),
Expand Down Expand Up @@ -442,6 +415,52 @@ func (cw *CommandWrapper) appendFlags(environ []string, flagSets ...*pflag.FlagS
return environ
}

// newTestID creates a new test ID from the provided test UID.
func newTestID(uid *uuid.UUID) string {
return fmt.Sprintf("%s%s", testIDIgnorePrefix, uid.String())
}

// ensureProcessChainLeaf ensures the process chain has at least one element. If the user didn't specify anything, add a
// default process to the chain.
func ensureProcessChainLeaf(testDesc *loader.Test) {
if testDesc.Context == nil {
testDesc.Context = &loader.TestContext{}
}
if len(testDesc.Context.Processes) == 0 && testDesc.Context.Container == nil {
testDesc.Context.Processes = []loader.ProcessContext{{}}
}
}

// extractTestUID extracts the test UID from the provided test ID.
func extractTestUID(testID string) (uuid.UUID, error) {
return uuid.Parse(strings.TrimPrefix(testID, testIDIgnorePrefix))
}

// logRunnerError logs the provided runner error using the provided logger.
func logRunnerError(logger logr.Logger, err error) {
var resBuildErr *test.ResourceBuildError
var stepBuildErr *test.StepBuildError
var resCreationErr *test.ResourceCreationError
var stepRunErr *test.StepBuildError

switch {
case errors.As(err, &resBuildErr):
logger.Error(resBuildErr.Err, "Error building test resource", "resourceName", resBuildErr.ResourceName,
"resourceIndex", resBuildErr.ResourceIndex)
case errors.As(err, &stepBuildErr):
logger.Error(stepBuildErr.Err, "Error building test step", "stepName", stepBuildErr.StepName,
"stepIndex", stepBuildErr.StepIndex)
case errors.As(err, &resCreationErr):
logger.Error(resCreationErr.Err, "Error creating test resource", "resourceName",
resCreationErr.ResourceName, "resourceIndex", resCreationErr.ResourceIndex)
case errors.As(err, &stepRunErr):
logger.Error(stepRunErr.Err, "Error running test step", "stepName", stepRunErr.StepName, "stepIndex",
stepRunErr.StepIndex)
default:
logger.Error(err, "Error running test")
}
}

// produceReport produces a report for the given test by using the provided tester.
func produceReport(wg *sync.WaitGroup, testr tester.Tester, testUID *uuid.UUID, testDesc *loader.Test,
reportFmt reportFormat) {
Expand Down
Loading