systemtest/apmservertest/command.go (163 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package apmservertest import ( "context" "crypto/fips140" "fmt" "log" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" ) // TODO(axw): add support for building/running the OSS apm-server. // ServerCommand returns a ServerCmd (wrapping os/exec) for running // apm-server with args. func ServerCommand(ctx context.Context, subcommand string, args ...string) *ServerCmd { binary, buildErr := BuildServerBinary(runtime.GOOS, runtime.GOARCH) if buildErr != nil { // Dummy command; Start etc. will return the build error. binary = "/usr/bin/false" } args = append([]string{subcommand}, args...) cmd := exec.CommandContext(ctx, binary, args...) cmd.SysProcAttr = serverCommandSysProcAttr return &ServerCmd{ Cmd: cmd, buildError: buildErr, } } // ServerCmd wraps an os/exec.Cmd, taking care of building apm-server // and cleaning up on close. type ServerCmd struct { *exec.Cmd buildError error tempdir string } // Run runs the apm-server command, and waits for it to exit. func (c *ServerCmd) Run() error { if err := c.Start(); err != nil { return err } return c.Wait() } // Output runs the apm-server command, waiting for it to exit // and returning its stdout. func (c *ServerCmd) Output() ([]byte, error) { if err := c.prestart(); err != nil { return nil, err } defer c.cleanup() return c.Cmd.Output() } // CombinedOutput runs the apm-server command, waiting for it to exit // and returning its combined stdout/stderr. func (c *ServerCmd) CombinedOutput() ([]byte, error) { if err := c.prestart(); err != nil { return nil, err } defer c.cleanup() return c.Cmd.CombinedOutput() } // Start starts the apm-server command, and returns immediately. func (c *ServerCmd) Start() error { if err := c.prestart(); err != nil { return err } if err := c.Cmd.Start(); err != nil { c.cleanup() return err } return nil } // Wait waits for the previously started apm-server command to exit. func (c *ServerCmd) Wait() error { defer c.cleanup() return c.Cmd.Wait() } // InterruptProcess sends an interrupt signal func (c *ServerCmd) InterruptProcess() error { return interruptProcess(c.Process) } func (c *ServerCmd) prestart() error { if c.buildError != nil { return c.buildError } if c.Dir == "" { if err := c.createTempDir(); err != nil { return err } } return nil } func (c *ServerCmd) createTempDir() error { tempdir, err := os.MkdirTemp("", "apm-server-systemtest") if err != nil { return err } if err := os.WriteFile(filepath.Join(tempdir, "apm-server.yml"), nil, 0644); err != nil { os.RemoveAll(tempdir) return err } // Symlink ingest/pipeline/definition.json into the temporary directory. pipelineDir := filepath.Join(tempdir, "ingest", "pipeline") if err := os.MkdirAll(pipelineDir, 0755); err != nil { os.RemoveAll(tempdir) return err } pipelineDefinitionFile := filepath.Join(filepath.Dir(c.Cmd.Path), "ingest", "pipeline", "definition.json") pipelineDefinitionSymlink := filepath.Join(pipelineDir, "definition.json") if err := os.Symlink(pipelineDefinitionFile, pipelineDefinitionSymlink); err != nil { if !os.IsExist(err) { os.RemoveAll(tempdir) return err } } c.tempdir = tempdir c.Dir = tempdir return nil } func (c *ServerCmd) cleanup() { if c.tempdir != "" { os.RemoveAll(c.tempdir) } } // BuildServerBinary builds the apm-server binary for the given GOOS // and GOARCH, returning its absolute path. func BuildServerBinary(goos, goarch string) (string, error) { apmServerBinaryMu.Lock() defer apmServerBinaryMu.Unlock() if binary := apmServerBinary[goos]; binary != "" { return binary, nil } repoRoot, err := getRepoRoot() if err != nil { return "", err } name := "apm-server" abspath := filepath.Join(repoRoot, "build", fmt.Sprintf("apm-server-%s-%s", goos, goarch)) if fips140.Enabled() { abspath += "-fips" name += "-fips" } if goos == "windows" { abspath += ".exe" } log.Printf("Building %s...", name) cmd := exec.Command("make", name) cmd.Dir = repoRoot cmd.Env = append(cmd.Env, os.Environ()...) cmd.Env = append(cmd.Env, "NOCP=1") // prevent race condition cmd.Env = append(cmd.Env, "GOOS="+goos, "GOARCH="+goarch) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return "", err } log.Println("Built", abspath) apmServerBinary[goos] = abspath return abspath, nil } func getRepoRoot() (string, error) { repoRootMu.Lock() defer repoRootMu.Unlock() if repoRoot != "" { return repoRoot, nil } // Build apm-server binary in the repo root. output, err := exec.Command("go", "list", "-m", "-f={{.Dir}}/..").Output() if err != nil { return "", err } repoRoot = filepath.Clean(strings.TrimSpace(string(output))) return repoRoot, nil } var ( apmServerBinaryMu sync.Mutex apmServerBinary = make(map[string]string) repoRootMu sync.Mutex repoRoot string )