diff --git a/cmd/declarative/config/config.go b/cmd/declarative/config/config.go index c0ca0de7..8f2b5a87 100644 --- a/cmd/declarative/config/config.go +++ b/cmd/declarative/config/config.go @@ -34,8 +34,8 @@ const ( DescriptionFlagName = "description" // TestIDFlagName is the name of the flag allowing to specify the test identifier. TestIDFlagName = "test-id" - // ProcLabelFlagName is the name of the flag allowing to specify a process label. - ProcLabelFlagName = "proc-label" + // LabelsFlagName is the name of the flag allowing to specify labels. + LabelsFlagName = "labels" // TimeoutFlagName is the name of the flag allowing to specify the test timeout. TimeoutFlagName = "timeout" ) @@ -51,8 +51,8 @@ type Config struct { DescriptionEnvKey string // TestIDEnvKey is the environment variable key corresponding to TestIDFlagName. TestIDEnvKey string - // ProcLabelEnvKey is the environment variable key corresponding to ProcLabelFlagName. - ProcLabelEnvKey string + // LabelsEnvKey is the environment variable key corresponding to LabelsFlagName. + LabelsEnvKey string // TimeoutEnvKey is the environment variable key corresponding to TimeoutFlagName. TimeoutEnvKey string @@ -68,11 +68,6 @@ type Config struct { // - the root process has no test ID // - the processes in the process chain but the last have the test ID in the form testIDIgnorePrefix // - the last process in the process chain has the test ID in the form - // A process having a test ID in the form (i.e.: the leaf process) is the only one that is monitored. - TestID string - // ProcLabel is the process label in the form test,child. It is used for logging purposes - // and to potentially generate the child process label. - ProcLabel string // TestsTimeout is the maximal duration of the tests. If running tests lasts more than TestsTimeout, the execution // of all pending tasks is canceled. TestsTimeout time.Duration @@ -82,6 +77,14 @@ type Config struct { ContainerBaseImageName string // ContainerImagePullPolicy is container image pull policy. ContainerImagePullPolicy builder.ImagePullPolicy + // + // Hidden flags + // + // A process having a test ID in the form (i.e.: the leaf process) is the only one that is monitored. + TestID string + // Labels is the string containing the comma-separated list of labels in the form =. It is used + // for logging purposes and to potentially generate the child process/container labels. + Labels string } var containerImagePullPolicies = map[builder.ImagePullPolicy][]string{ @@ -98,7 +101,7 @@ func New(cmd *cobra.Command, declarativeEnvKey, envKeysPrefix string) *Config { DescriptionFileEnvKey: envKeyFromFlagName(envKeysPrefix, DescriptionFileFlagName), DescriptionEnvKey: envKeyFromFlagName(envKeysPrefix, DescriptionFlagName), TestIDEnvKey: envKeyFromFlagName(envKeysPrefix, TestIDFlagName), - ProcLabelEnvKey: envKeyFromFlagName(envKeysPrefix, ProcLabelFlagName), + LabelsEnvKey: envKeyFromFlagName(envKeysPrefix, LabelsFlagName), TimeoutEnvKey: envKeyFromFlagName(envKeysPrefix, TimeoutFlagName), } commonConf.initFlags(cmd) @@ -114,16 +117,6 @@ func (c *Config) initFlags(cmd *cobra.Command) { flags.StringVarP(&c.TestsDescription, DescriptionFlagName, "d", "", "The YAML-formatted tests description string specifying the tests to be run") cmd.MarkFlagsMutuallyExclusive(DescriptionFileFlagName, DescriptionFlagName) - - flags.StringVar(&c.TestID, TestIDFlagName, "", - "(used during process chain building) The test identifier in the form . It is "+ - "used to propagate the test UID to child processes in the process chain") - flags.StringVar(&c.ProcLabel, ProcLabelFlagName, "", - "(used during process chain building) The process label in the form test,child. "+ - "It is used for logging purposes and to potentially generate the child process label") - _ = flags.MarkHidden(TestIDFlagName) - _ = flags.MarkHidden(ProcLabelFlagName) - flags.DurationVarP(&c.TestsTimeout, TimeoutFlagName, "t", time.Minute, "The maximal duration of the tests. If running tests lasts more than testsTimeout, the execution of "+ "all pending tasks is canceled") @@ -136,6 +129,16 @@ func (c *Config) initFlags(cmd *cobra.Command) { flags.Var(enumflag.New(&c.ContainerImagePullPolicy, "container-image-pull-policy", containerImagePullPolicies, enumflag.EnumCaseInsensitive), "container-image-pull-policy", "The container image pull policy; can be 'always', 'never' or 'ifnotpresent'") + + // Hidden flags. + flags.StringVar(&c.TestID, TestIDFlagName, "", + "(used during process chain building) The test identifier in the form . It is "+ + "used to propagate the test UID to child processes/container in the process chain") + flags.StringVar(&c.Labels, LabelsFlagName, "", + "(used during process chain building) The list of comma-separated labels in the form =. "+ + "It is used for logging purposes and to potentially generate the child process/container labels") + _ = flags.MarkHidden(TestIDFlagName) + _ = flags.MarkHidden(LabelsFlagName) } // envKeyFromFlagName converts the provided flag name into the corresponding environment variable key. diff --git a/cmd/declarative/test/test.go b/cmd/declarative/test/test.go index ba13818c..4c24acfe 100644 --- a/cmd/declarative/test/test.go +++ b/cmd/declarative/test/test.go @@ -21,8 +21,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" - "strconv" "strings" "sync" "time" @@ -36,6 +34,7 @@ import ( "github.com/falcosecurity/event-generator/cmd/declarative/config" "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" "github.com/falcosecurity/event-generator/pkg/test" testbuilder "github.com/falcosecurity/event-generator/pkg/test/builder" "github.com/falcosecurity/event-generator/pkg/test/loader" @@ -181,17 +180,13 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { testID := cw.TestID isRootProcess := testID == "" - procLabelInfo, err := parseProcLabel(cw.ProcLabel) + labels, err := label.ParseSet(cw.Labels) if err != nil { - logger.Error(err, "Error parsing process label") + logger.Error(err, "Error parsing labels") exitAndCancel() } - if procLabelInfo == nil { - logger = logger.WithName("root") - } else { - logger = logger.WithName(procLabelInfo.testName).WithName(procLabelInfo.childName) - } + logger = enrichLogger(logger, labels) description, err := loadTestsDescription(logger, cw.TestsDescriptionFile, cw.TestsDescription) if err != nil { @@ -256,15 +251,21 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { // Prepare parameters shared by runners. runnerLogger := logger.WithName("runner") runnerEnviron := cw.buildRunnerEnviron(cmd) - var runnerProcLabel string - if procLabelInfo != nil { - runnerProcLabel = fmt.Sprintf("%s,%s", procLabelInfo.testName, procLabelInfo.childName) + var runnerLabels *label.Set + if labels != nil { + runnerLabels = labels.Clone() } // Build and run the tests. for testIndex := range description.Tests { testDesc := &description.Tests[testIndex] + // If this process belongs to a test process chain, override the logged test index in order to match its + // absolute index among all the test descriptions. + if labels != nil { + testIndex = labels.TestIndex + } + logger := logger.WithValues("testName", testDesc.Name, "testIndex", testIndex) var testUID uuid.UUID @@ -292,6 +293,7 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { } logger = logger.WithValues("testUid", testUID) + runnerLogger := runnerLogger.WithValues("testName", testDesc.Name, "testIndex", testIndex, "testUid", testUID) runnerDescription := &runner.Description{ Environ: runnerEnviron, @@ -299,8 +301,8 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { TestDescriptionFileEnvKey: cw.DescriptionFileEnvKey, TestIDEnvKey: cw.TestIDEnvKey, TestIDIgnorePrefix: testIDIgnorePrefix, - ProcLabelEnvKey: cw.ProcLabelEnvKey, - ProcLabel: runnerProcLabel, + LabelsEnvKey: cw.LabelsEnvKey, + Labels: runnerLabels, } runnerInstance, err := runnerBuilder.Build(testDesc.Runner, runnerLogger, runnerDescription) if err != nil { @@ -308,12 +310,6 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { exitAndCancel() } - // If this process belongs to a test process chain, override the logged test index in order to match its - // absolute index among all the test descriptions. - if !isRootProcess { - testIndex = procLabelInfo.testIndex - } - logger.Info("Starting test execution...") if err := runnerInstance.Run(ctx, testID, testIndex, testDesc); err != nil { @@ -352,45 +348,24 @@ func (cw *CommandWrapper) run(cmd *cobra.Command, _ []string) { testerWaitGroup.Wait() } -var ( - // procLabelRegex defines the process label format and allows to extract the embedded test and child indexes. - procLabelRegex = regexp.MustCompile(`^test(\d+),child(\d+)$`) - errProcLabelRegex = fmt.Errorf("process label must comply with %q regex", procLabelRegex.String()) -) - -// processLabelInfo contains information regarding the process label. -type processLabelInfo struct { - testName string - testIndex int - childName string - childIndex int -} - -// parseProcLabel parses the process label and returns information on it. -func parseProcLabel(procLabel string) (*processLabelInfo, error) { - if procLabel == "" { - return nil, nil - } - - match := procLabelRegex.FindStringSubmatch(procLabel) - if match == nil { - return nil, errProcLabelRegex +func enrichLogger(logger logr.Logger, labels *label.Set) logr.Logger { + if labels == nil { + return logger.WithName("root") } - // No errors can occur, since we have already verified through regex that they are numbers. - testIndex, _ := strconv.Atoi(match[1]) - childIndex, _ := strconv.Atoi(match[2]) - - parts := strings.Split(procLabel, ",") - - procLabelInfo := &processLabelInfo{ - testName: parts[0], - testIndex: testIndex, - childName: parts[1], - childIndex: childIndex, + testName := fmt.Sprintf("test%d", labels.TestIndex) + logger = logger.WithName(testName) + if labels.IsContainer { + logger = logger.WithName("cont") + if imageName := labels.ImageName; imageName != "" { + logger = logger.WithValues("imageName", imageName) + } + if containerName := labels.ContainerName; containerName != "" { + logger = logger.WithValues("containerName", containerName) + } } - - return procLabelInfo, nil + procName := fmt.Sprintf("proc%d", labels.ProcIndex) + return logger.WithName(procName) } // loadTestsDescription loads the YAML tests description from a different source, depending on the content of the diff --git a/pkg/test/runner/host/host.go b/pkg/test/runner/host/host.go index 5aa07886..78c2e422 100644 --- a/pkg/test/runner/host/host.go +++ b/pkg/test/runner/host/host.go @@ -19,12 +19,12 @@ import ( "context" "fmt" "os" - "strconv" "strings" "github.com/go-logr/logr" "github.com/falcosecurity/event-generator/pkg/container" + "github.com/falcosecurity/event-generator/pkg/label" "github.com/falcosecurity/event-generator/pkg/process" "github.com/falcosecurity/event-generator/pkg/test" "github.com/falcosecurity/event-generator/pkg/test/loader" @@ -72,8 +72,8 @@ func New(logger logr.Logger, testBuilder test.Builder, containerBuilder containe return nil, fmt.Errorf("description.TestIDIgnorePrefix must not be empty") } - if description.ProcLabelEnvKey == "" { - return nil, fmt.Errorf("description.ProcLabelEnvKey must not be empty") + if description.LabelsEnvKey == "" { + return nil, fmt.Errorf("description.LabelsEnvKey must not be empty") } r := &hostRunner{ @@ -125,6 +125,14 @@ func (r *hostRunner) Run(ctx context.Context, testID string, testIndex int, test // delegateToContainer delegates the execution of the test to a container, created and tuned as per test specification. func (r *hostRunner) delegateToContainer(ctx context.Context, testID string, testIndex int, testDesc *loader.Test) error { + // Initialize labels for the container's process. + labels := r.Labels + if labels == nil { + labels = &label.Set{TestIndex: testIndex, ProcIndex: 0, IsContainer: true} + } else { + labels.ProcIndex++ + } + containerContext := popContainer(testDesc.Context) // Configure the container. @@ -134,13 +142,15 @@ func (r *hostRunner) delegateToContainer(ctx context.Context, testID string, tes if imageName := containerContext.Image; imageName != nil { containerBuilder.SetImageName(*imageName) + labels.ImageName = *imageName } if containerName := containerContext.Name; containerName != nil { containerBuilder.SetContainerName(*containerName) + labels.ContainerName = *containerName } - containerEnv, err := r.buildEnv(testID, testIndex, testDesc, containerContext.Env, false) + containerEnv, err := r.buildEnv(testID, testDesc, containerContext.Env, false, labels) if err != nil { return fmt.Errorf("error building container environment variables set: %w", err) } @@ -168,9 +178,9 @@ func popContainer(testContext *loader.TestContext) *loader.ContainerContext { } // buildEnv builds the environment variable set for a given process, leveraging the provided test data and the -// additional user-provide environment variables. -func (r *hostRunner) buildEnv(testID string, testIndex int, testDesc *loader.Test, userEnv map[string]string, - isLastProcess bool) ([]string, error) { +// additional user-provide environment variables and labels. +func (r *hostRunner) buildEnv(testID string, testDesc *loader.Test, userEnv map[string]string, + isLastProcess bool, labels *label.Set) ([]string, error) { env := r.Environ // Add the user-provided environment variable. @@ -191,18 +201,18 @@ func (r *hostRunner) buildEnv(testID string, testIndex int, testDesc *loader.Tes } testIDEnvVar := buildEnvVar(r.TestIDEnvKey, testID) - // Set process label environment variable. - procLabel, err := r.buildProcLabel(testIndex) + // Set labels environment variable. + labelsValue, err := marshalLabels(labels) if err != nil { - return nil, fmt.Errorf("error building process label: %w", err) + return nil, fmt.Errorf("error serializing labels: %w", err) } - procLabelEnvVar := buildEnvVar(r.ProcLabelEnvKey, procLabel) + labelsEnvVar := buildEnvVar(r.LabelsEnvKey, labelsValue) // Override test description file environment variable to avoid conflicts with the test description environment // variable. descriptionFileEnvVar := buildEnvVar(r.TestDescriptionFileEnvKey, "") - env = append(env, descriptionEnvVar, testIDEnvVar, procLabelEnvVar, descriptionFileEnvVar) + env = append(env, descriptionEnvVar, testIDEnvVar, labelsEnvVar, descriptionFileEnvVar) return env, nil } @@ -227,27 +237,13 @@ func (r *hostRunner) getTestUID(testID string) string { return strings.TrimPrefix(testID, r.TestIDIgnorePrefix) } -// buildProcLabel builds a process label. If the current process Label is not defined, it uses the provided testIndex to -// create a new one; otherwise, given the process label in the form testName,child, it returns -// testName,child. -func (r *hostRunner) buildProcLabel(testIndex int) (string, error) { - procLabel := r.ProcLabel - if procLabel == "" { - return fmt.Sprintf("test%d,child0", testIndex), nil - } - - labelParts := strings.Split(procLabel, ",") - if len(labelParts) != 2 { - return "", fmt.Errorf("cannot parse process label") - } - - testName, procName := labelParts[0], labelParts[1] - childIndex, err := strconv.Atoi(strings.TrimPrefix(procName, "child")) - if err != nil { - return "", fmt.Errorf("error parsing process name in process label %q: %w", procName, err) +// marshalLabels returns the serialized labels. +func marshalLabels(labels *label.Set) (string, error) { + sb := &strings.Builder{} + if err := labels.Write(sb); err != nil { + return "", err } - - return fmt.Sprintf("%s,child%d", testName, childIndex+1), nil + return sb.String(), nil } // delegateToProcess delegates the execution of the test to a process, created and tuned as per test specification. @@ -255,8 +251,15 @@ func (r *hostRunner) delegateToProcess(ctx context.Context, testID string, testI firstProcess := popFirstProcessContext(testDesc.Context) isLastProcess := len(testDesc.Context.Processes) == 0 + labels := r.Labels + if labels == nil { + labels = &label.Set{TestIndex: testIndex, ProcIndex: 0, IsContainer: false} + } else { + labels.ProcIndex++ + } + // Evaluate process environment variables. - procEnv, err := r.buildEnv(testID, testIndex, testDesc, firstProcess.Env, isLastProcess) + procEnv, err := r.buildEnv(testID, testDesc, firstProcess.Env, isLastProcess, labels) if err != nil { return fmt.Errorf("error building process environment variables set: %w", err) } diff --git a/pkg/test/runner/runner.go b/pkg/test/runner/runner.go index 37c057fd..5431d2a5 100644 --- a/pkg/test/runner/runner.go +++ b/pkg/test/runner/runner.go @@ -20,6 +20,7 @@ import ( "github.com/go-logr/logr" + "github.com/falcosecurity/event-generator/pkg/label" "github.com/falcosecurity/event-generator/pkg/test/loader" ) @@ -50,9 +51,8 @@ type Description struct { TestIDEnvKey string // TestIDIgnorePrefix is the optional testID prefix value. TestIDIgnorePrefix string - // ProcLabelEnvKey is the key identifying the environment variable used to store the process label in the form - // "test,child". - ProcLabelEnvKey string - // ProcLabel is the current process label. - ProcLabel string + // LabelsEnvKey is the key identifying the environment variable used to store the labels. + LabelsEnvKey string + // Labels is the set of process labels. + Labels *label.Set }