From e9ae6a08e81facab7c238f8542e7ca2428513f77 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Tue, 25 Nov 2025 14:18:58 +1030 Subject: [PATCH 1/4] internal/testrunner/runners/script: provide mechanism to inspect agent containers --- docs/howto/script_testing.md | 4 ++-- internal/testrunner/script/agents.go | 20 ++++++++++--------- .../_dev/test/scripts/get_docs_external.txt | 9 ++++++++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/docs/howto/script_testing.md b/docs/howto/script_testing.md index 63d0c86be..655c30064 100644 --- a/docs/howto/script_testing.md +++ b/docs/howto/script_testing.md @@ -47,7 +47,7 @@ a stack, starting agents and services and validating results. - `get_policy [-profile ] [-timeout ] `: print the details for a policy - agent commands: - - `install_agent [-profile ] [-timeout ] []`: install an Elastic Agent policy, setting the environment variable named in the positional argument + - `install_agent [-profile ] [-timeout ] [-container_name ] [-network_name ]`: install an Elastic Agent policy, setting the environment variables named in the container_name and network_name arguments - `uninstall_agent [-profile ] [-timeout ]`: remove an installed Elastic Agent policy - package commands: @@ -110,7 +110,7 @@ As an example, a basic system test could be expressed as follows. use_stack -profile ${CONFIG_PROFILES}/${PROFILE} # Install an agent. -install_agent -profile ${CONFIG_PROFILES}/${PROFILE} NETWORK_NAME +install_agent -profile ${CONFIG_PROFILES}/${PROFILE} -network_name NETWORK_NAME # Bring up a docker container. # diff --git a/internal/testrunner/script/agents.go b/internal/testrunner/script/agents.go index c579bd86a..8e5750033 100644 --- a/internal/testrunner/script/agents.go +++ b/internal/testrunner/script/agents.go @@ -9,6 +9,7 @@ import ( "errors" "flag" "fmt" + "path/filepath" "time" "github.com/rogpeppe/go-internal/testscript" @@ -47,14 +48,11 @@ func installAgent(ts *testscript.TestScript, neg bool, args []string) { flg := flag.NewFlagSet("install", flag.ContinueOnError) profName := flg.String("profile", "default", "profile name") timeout := flg.Duration("timeout", 0, "timeout (zero or lower indicates no timeout)") + containerNameLabel := flg.String("container_name", "", "environment variable name to place container name in") + networkNameLabel := flg.String("network_name", "", "environment variable name to place network name in") ts.Check(flg.Parse(args)) - if flg.NArg() != 0 && flg.NArg() != 1 { - ts.Fatalf("usage: install_agent [-profile ] [-timeout ] []") - } - - var networkNameLabel string - if flg.NArg() == 1 { - networkNameLabel = flg.Arg(0) + if flg.NArg() != 0 { + ts.Fatalf("usage: install_agent [-profile ] [-timeout ] [-container_name ] [-network_name ]") } stk, ok := stacks[*profName] @@ -120,8 +118,12 @@ func installAgent(ts *testscript.TestScript, neg bool, args []string) { // ELASTIC_PACKAGE_CA_CERT is set. ¯\_(ツ)_/¯ installed.deployed, err = dep.SetUp(ctx, info) ts.Check(decoratedWith("setting up agent", err)) - if networkNameLabel != "" { - ts.Setenv(networkNameLabel, installed.deployed.Info().NetworkName) + depInfo := installed.deployed.Info() + if *networkNameLabel != "" { + ts.Setenv(*networkNameLabel, depInfo.NetworkName) + } + if *containerNameLabel != "" { + ts.Setenv(*containerNameLabel, fmt.Sprintf("elastic-package-agent-%s-%s-%s-%s-1", filepath.Base(pkgRoot), ds, info.Test.RunID, depInfo.Name)) } polID := installed.deployed.Info().Policy.ID ts.Check(decoratedWith("getting kibana agent", doKibanaAgent(ctx, stk.kibana, func(a kibana.Agent) (bool, error) { diff --git a/test/packages/other/with_script/data_stream/first/_dev/test/scripts/get_docs_external.txt b/test/packages/other/with_script/data_stream/first/_dev/test/scripts/get_docs_external.txt index 2574a4a2d..eba3a7d68 100644 --- a/test/packages/other/with_script/data_stream/first/_dev/test/scripts/get_docs_external.txt +++ b/test/packages/other/with_script/data_stream/first/_dev/test/scripts/get_docs_external.txt @@ -8,13 +8,20 @@ use_stack -profile ${CONFIG_PROFILES}/${PROFILE} cmpenv stdout want_use.json # Install an agent. -install_agent -profile ${CONFIG_PROFILES}/${PROFILE} NETWORK_NAME +install_agent -profile ${CONFIG_PROFILES}/${PROFILE} -container_name CONTAINER_NAME -network_name NETWORK_NAME ! stderr . cmp stdout want_agent_up.text +# Check the container name is valid. +exec echo ${CONTAINER_NAME} +stdout 'elastic-package-agent-with_script-first-[0-9]+-elastic-agent-1' # Check the network name is valid. exec echo ${NETWORK_NAME} stdout 'elastic-package-agent-with_script-first-[0-9]+_default' +# Demonstrate we can interact with the running agent container. +exec docker exec ${CONTAINER_NAME} ls +stdout elastic-agent + # Bring up a docker container and check that we get the expected output. docker_up -profile ${CONFIG_PROFILES}/${PROFILE} -network ${NETWORK_NAME} test-hits ! stderr . From b126a3d1c509125d01fe4d5c43ffb7c80ae27164 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Thu, 27 Nov 2025 10:29:14 +1030 Subject: [PATCH 2/4] move project name rendering to agentdeployer package --- internal/agentdeployer/agent.go | 7 ++++++- internal/testrunner/script/agents.go | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/agentdeployer/agent.go b/internal/agentdeployer/agent.go index b2391c50b..794db8c1e 100644 --- a/internal/agentdeployer/agent.go +++ b/internal/agentdeployer/agent.go @@ -133,7 +133,7 @@ func (d *DockerComposeAgentDeployer) SetUp(ctx context.Context, agentInfo AgentI return nil, fmt.Errorf("could not create resources for custom agent: %w", err) } - composeProjectName := fmt.Sprintf("elastic-package-agent-%s-%s", d.agentName(), agentInfo.Test.RunID) + composeProjectName := d.ProjectName(agentInfo.Test.RunID) agent := dockerComposeDeployedAgent{ ymlPaths: []string{filepath.Join(configDir, dockerTestAgentDockerCompose)}, @@ -228,6 +228,11 @@ func (d *DockerComposeAgentDeployer) SetUp(ctx context.Context, agentInfo AgentI return &agent, nil } +// ProjectName returns the Docker Compose project name for the agent. +func (d *DockerComposeAgentDeployer) ProjectName(runID string) string { + return fmt.Sprintf("elastic-package-agent-%s-%s", d.agentName(), runID) +} + func (d *DockerComposeAgentDeployer) agentHostname() string { return fmt.Sprintf("%s-%s", dockerTestAgentServiceName, d.agentRunID) } diff --git a/internal/testrunner/script/agents.go b/internal/testrunner/script/agents.go index 8e5750033..d5d1502f6 100644 --- a/internal/testrunner/script/agents.go +++ b/internal/testrunner/script/agents.go @@ -123,7 +123,7 @@ func installAgent(ts *testscript.TestScript, neg bool, args []string) { ts.Setenv(*networkNameLabel, depInfo.NetworkName) } if *containerNameLabel != "" { - ts.Setenv(*containerNameLabel, fmt.Sprintf("elastic-package-agent-%s-%s-%s-%s-1", filepath.Base(pkgRoot), ds, info.Test.RunID, depInfo.Name)) + ts.Setenv(*containerNameLabel, fmt.Sprintf("%s-%s-1", dep.ProjectName(info.Test.RunID), depInfo.Name)) } polID := installed.deployed.Info().Policy.ID ts.Check(decoratedWith("getting kibana agent", doKibanaAgent(ctx, stk.kibana, func(a kibana.Agent) (bool, error) { From cecdc18b3d5ad77954f630102f8838a36f165a83 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Thu, 27 Nov 2025 10:41:21 +1030 Subject: [PATCH 3/4] add compile_registry_state command --- docs/howto/script_testing.md | 1 + internal/testrunner/script/agents.go | 84 ++++++++++++- internal/testrunner/script/script.go | 1 + .../first/_dev/test/scripts/registry.txt | 113 ++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 test/packages/other/with_script/data_stream/first/_dev/test/scripts/registry.txt diff --git a/docs/howto/script_testing.md b/docs/howto/script_testing.md index 655c30064..0ded9bc24 100644 --- a/docs/howto/script_testing.md +++ b/docs/howto/script_testing.md @@ -49,6 +49,7 @@ a stack, starting agents and services and validating results. - agent commands: - `install_agent [-profile ] [-timeout ] [-container_name ] [-network_name ]`: install an Elastic Agent policy, setting the environment variables named in the container_name and network_name arguments - `uninstall_agent [-profile ] [-timeout ]`: remove an installed Elastic Agent policy + - `compile_registry_state [-start ] `: compile a Filebeat registry log.json file into a registry state and print it to stdout - package commands: - `add_package [-profile ] [-timeout ]`: add the current package's assets diff --git a/internal/testrunner/script/agents.go b/internal/testrunner/script/agents.go index d5d1502f6..ed5e35868 100644 --- a/internal/testrunner/script/agents.go +++ b/internal/testrunner/script/agents.go @@ -6,10 +6,12 @@ package script import ( "context" + "encoding/json" "errors" "flag" "fmt" - "path/filepath" + "io" + "os" "time" "github.com/rogpeppe/go-internal/testscript" @@ -234,3 +236,83 @@ func deletePolicies(ctx context.Context, cli *kibana.Client, a *installedAgent) } return errors.Join(errs...) } + +func compileRegistryState(ts *testscript.TestScript, neg bool, args []string) { + if neg { + ts.Fatalf("unsupported: ! get_registry_state") + } + clearStdStreams(ts) + flg := flag.NewFlagSet("uninstall", flag.ContinueOnError) + first := flg.Int64("start", 0, "first registry log operation ID to use") + pretty := flg.Bool("pretty", false, "pretty print the registry") + ts.Check(flg.Parse(args)) + if flg.NArg() != 1 { + ts.Fatalf("usage: get_registry_state [-start ] ") + } + var choose func(int64) bool + if *first != 0 { + choose = func(i int64) bool { + return i >= *first + } + } + f, err := os.Open(ts.MkAbs(flg.Arg(0))) + ts.Check(decoratedWith("opening registry log", err)) + defer f.Close() + + s := make(map[string]any) + ts.Check(decoratedWith("compiling state", compileStateInto(s, f, choose))) + var msg []byte + if *pretty { + msg, err = json.MarshalIndent(s, "", " ") + } else { + msg, err = json.Marshal(s) + } + ts.Check(decoratedWith("marshaling registry", err)) + fmt.Fprintf(ts.Stdout(), "%s\n", msg) +} + +func compileStateInto(dst map[string]any, r io.Reader, choose func(int64) bool) error { + type action struct { + Operation string `json:"op"` + ID int64 `json:"id"` + } + + type delta struct { + Key string `json:"K"` + Val map[string]any `json:"V"` + } + + dec := json.NewDecoder(r) + dec.DisallowUnknownFields() + for { + var ( + a action + d delta + ) + err := dec.Decode(&a) + if err != nil { + if err == io.EOF { + return nil + } + return err + } + err = dec.Decode(&d) + if err != nil { + if err == io.EOF { + return io.ErrUnexpectedEOF + } + return err + } + if choose != nil && !choose(a.ID) { + continue + } + switch a.Operation { + case "set": + dst[d.Key] = d.Val + case "remove": + delete(dst, d.Key) + default: + return fmt.Errorf("unknown operation: %q", a.Operation) + } + } +} diff --git a/internal/testrunner/script/script.go b/internal/testrunner/script/script.go index c5af0398f..688cfb2cf 100644 --- a/internal/testrunner/script/script.go +++ b/internal/testrunner/script/script.go @@ -211,6 +211,7 @@ func Run(dst io.Writer, cmd *cobra.Command, args []string) error { "dump_logs": dumpLogs, "match_file": match, "get_policy": getPolicyCommand, + "compile_registry_state": compileRegistryState, }, Setup: func(e *testscript.Env) error { e.Setenv("PROFILE", config.CurrentProfile()) diff --git a/test/packages/other/with_script/data_stream/first/_dev/test/scripts/registry.txt b/test/packages/other/with_script/data_stream/first/_dev/test/scripts/registry.txt new file mode 100644 index 000000000..5b20ffb07 --- /dev/null +++ b/test/packages/other/with_script/data_stream/first/_dev/test/scripts/registry.txt @@ -0,0 +1,113 @@ +compile_registry_state -pretty keys_log.json +cmp stdout want_keys_log_0.json + +compile_registry_state -pretty -start=3 keys_log.json +cmp stdout want_keys_log_3.json + +compile_registry_state -pretty key_log.json +cmp stdout want_key_log.json + +compile_registry_state -pretty keys_removed_log.json +cmp stdout want_keys_removed_log.json + +-- keys_log.json -- +{"op":"set", "id": 1} +{"K":"key0","V":{"a":0}} +{"op":"set", "id": 2} +{"K":"key1","V":{"a":1}} +{"op":"set", "id": 3} +{"K":"key2","V":{"a":2}} +{"op":"set", "id": 4} +{"K":"key3","V":{"a":3}} +{"op":"set", "id": 5} +{"K":"key4","V":{"a":4}} +{"op":"set", "id": 6} +{"K":"key5","V":{"a":5}} +-- want_keys_log_0.json -- +{ + "key0": { + "a": 0 + }, + "key1": { + "a": 1 + }, + "key2": { + "a": 2 + }, + "key3": { + "a": 3 + }, + "key4": { + "a": 4 + }, + "key5": { + "a": 5 + } +} +-- want_keys_log_3.json -- +{ + "key2": { + "a": 2 + }, + "key3": { + "a": 3 + }, + "key4": { + "a": 4 + }, + "key5": { + "a": 5 + } +} +-- key_log.json -- +{"op":"set", "id": 1} +{"K":"key0","V":{"a":0}} +{"op":"set", "id": 2} +{"K":"key0","V":{"a":1}} +{"op":"set", "id": 3} +{"K":"key0","V":{"a":2}} +{"op":"set", "id": 4} +{"K":"key0","V":{"a":3}} +{"op":"set", "id": 5} +{"K":"key0","V":{"a":4}} +{"op":"set", "id": 6} +{"K":"key0","V":{"a":5}} +-- want_key_log.json -- +{ + "key0": { + "a": 5 + } +} +-- keys_removed_log.json -- +{"op":"set", "id": 1} +{"K":"key0","V":{"a":0}} +{"op":"set", "id": 2} +{"K":"key1","V":{"a":1}} +{"op":"set", "id": 3} +{"K":"key2","V":{"a":2}} +{"op":"set", "id": 4} +{"K":"key3","V":{"a":3}} +{"op":"set", "id": 5} +{"K":"key4","V":{"a":4}} +{"op":"set", "id": 6} +{"K":"key5","V":{"a":5}} +{"op":"remove", "id": 7} +{"K":"key3"} +-- want_keys_removed_log.json -- +{ + "key0": { + "a": 0 + }, + "key1": { + "a": 1 + }, + "key2": { + "a": 2 + }, + "key4": { + "a": 4 + }, + "key5": { + "a": 5 + } +} From 46595e09283bc4aa765b19a703bcb61d77ab96f7 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Tue, 2 Dec 2025 06:29:48 +1030 Subject: [PATCH 4/4] address pr comment --- internal/testrunner/script/agents.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/testrunner/script/agents.go b/internal/testrunner/script/agents.go index ed5e35868..59f619b4c 100644 --- a/internal/testrunner/script/agents.go +++ b/internal/testrunner/script/agents.go @@ -239,7 +239,7 @@ func deletePolicies(ctx context.Context, cli *kibana.Client, a *installedAgent) func compileRegistryState(ts *testscript.TestScript, neg bool, args []string) { if neg { - ts.Fatalf("unsupported: ! get_registry_state") + ts.Fatalf("unsupported: ! compile_registry_state") } clearStdStreams(ts) flg := flag.NewFlagSet("uninstall", flag.ContinueOnError) @@ -247,7 +247,7 @@ func compileRegistryState(ts *testscript.TestScript, neg bool, args []string) { pretty := flg.Bool("pretty", false, "pretty print the registry") ts.Check(flg.Parse(args)) if flg.NArg() != 1 { - ts.Fatalf("usage: get_registry_state [-start ] ") + ts.Fatalf("usage: compile_registry_state [-start ] ") } var choose func(int64) bool if *first != 0 {