// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package main // This is a test application that is used by TestExecServer_Run to verify // the behavior of vault agent running as a process supervisor. // // The app will automatically exit after 1 minute or the --stop-after interval, // whichever comes first. It also can serve its loaded environment variables on // the given --port. This app will also return the given --exit-code and // terminate on SIGTERM unless --use-sigusr1 is specified. import ( "bytes" "context" "encoding/json" "errors" "flag" "fmt" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" ) var ( port uint sleepAfterStopSignal time.Duration useSigusr1StopSignal bool stopAfter time.Duration exitCode int logToStdout bool ) func init() { flag.UintVar(&port, "port", 34000, "port to run the test app on") flag.DurationVar(&sleepAfterStopSignal, "sleep-after-stop-signal", 1*time.Second, "time to sleep after getting the signal before exiting") flag.BoolVar(&useSigusr1StopSignal, "use-sigusr1", false, "use SIGUSR1 as the stop signal, instead of the default SIGTERM") flag.DurationVar(&stopAfter, "stop-after", 0, "stop the process after duration (overrides all other flags if set)") flag.IntVar(&exitCode, "exit-code", 0, "exit code to return when this script exits") flag.BoolVar(&logToStdout, "log-to-stdout", false, "log to stdout instead of stderr") } type Response struct { EnvironmentVariables map[string]string `json:"environment_variables"` ProcessID int `json:"process_id"` } func newResponse() Response { respEnv := make(map[string]string, len(os.Environ())) for _, envVar := range os.Environ() { tokens := strings.Split(envVar, "=") respEnv[tokens[0]] = tokens[1] } return Response{ EnvironmentVariables: respEnv, ProcessID: os.Getpid(), } } func handler(w http.ResponseWriter, r *http.Request) { var buf bytes.Buffer encoder := json.NewEncoder(&buf) if r.URL.Query().Get("pretty") == "1" { encoder.SetIndent("", " ") } if err := encoder.Encode(newResponse()); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, _ = w.Write(buf.Bytes()) } func main() { flag.Parse() logOut := os.Stderr if logToStdout { logOut = os.Stdout } logger := log.New(logOut, "test-app: ", log.LstdFlags) logger.Printf("running on port %d", port) if err := run(logger); err != nil { log.Fatalf("error: %v\n", err) } logger.Printf("exit code: %d\n", exitCode) os.Exit(exitCode) } func run(logger *log.Logger) error { /* */ logger.Println("run: started") defer logger.Println("run: done") ctx, cancelContextFunc := context.WithTimeout(context.Background(), 60*time.Second) defer cancelContextFunc() server := http.Server{ Addr: fmt.Sprintf(":%d", port), Handler: http.HandlerFunc(handler), ReadTimeout: 20 * time.Second, WriteTimeout: 20 * time.Second, IdleTimeout: 20 * time.Second, } doneCh := make(chan struct{}) go func() { defer close(doneCh) stopSignal := make(chan os.Signal, 1) if useSigusr1StopSignal { signal.Notify(stopSignal, syscall.SIGUSR1) } else { signal.Notify(stopSignal, syscall.SIGTERM) } select { case <-ctx.Done(): logger.Println("context done: exiting") case s := <-stopSignal: logger.Printf("signal %q: received\n", s) if sleepAfterStopSignal > 0 { logger.Printf("signal %q: sleeping for %v simulate cleanup\n", s, sleepAfterStopSignal) time.Sleep(sleepAfterStopSignal) } case <-time.After(stopAfter): logger.Printf("stopping after: %v\n", stopAfter) } if err := server.Shutdown(context.Background()); err != nil { log.Printf("server shutdown error: %v", err) } }() logger.Printf("server %s: started\n", server.Addr) if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { return fmt.Errorf("could not start the server: %v", err) } logger.Printf("server %s: done\n", server.Addr) <-doneCh return nil }