diff --git a/internal/nomad/api_querier.go b/internal/nomad/api_querier.go index fdf9356..3e487ab 100644 --- a/internal/nomad/api_querier.go +++ b/internal/nomad/api_querier.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/getsentry/sentry-go" "github.com/gorilla/websocket" nomadApi "github.com/hashicorp/nomad/api" "github.com/openHPI/poseidon/internal/config" @@ -112,13 +113,18 @@ func (nc *nomadAPIClient) Execute(runnerID string, } var exitCode int - logging.StartSpan("nomad.execute.exec", "Execute Command in Allocation", ctx, func(ctx context.Context) { - debugWriter := NewSentryDebugWriter(stdout, ctx) - commands := []string{"/bin/bash", "-c", cmd} - exitCode, err = nc.client.Allocations(). - Exec(ctx, allocation, TaskName, tty, commands, stdin, debugWriter, stderr, nil, nil) - debugWriter.Close(exitCode) - }) + span := sentry.StartSpan(ctx, "nomad.execute.exec") + span.Description = "Execute Command in Allocation" + + debugWriter := NewSentryDebugWriter(stdout, span.Context()) + commands := []string{"/bin/bash", "-c", cmd} + exitCode, err = nc.client.Allocations(). + Exec(span.Context(), allocation, TaskName, tty, commands, stdin, debugWriter, stderr, nil, nil) + debugWriter.Close(exitCode) + + span.SetData("command", cmd) + span.Finish() + switch { case err == nil: return exitCode, nil diff --git a/internal/nomad/nomad.go b/internal/nomad/nomad.go index 38eb2d9..61580a1 100644 --- a/internal/nomad/nomad.go +++ b/internal/nomad/nomad.go @@ -474,7 +474,8 @@ const ( ) func prepareCommandWithoutTTY(command string, privilegedExecution bool) string { - command = setInnerDebugMessages(command, false) + const commandFieldAfterEnv = 4 // instead of "env CODEOCEAN=true /bin/bash -c sleep infinity" just "sleep infinity". + command = setInnerDebugMessages(command, commandFieldAfterEnv, -1) command = setUserCommand(command, privilegedExecution) command = unsetEnvironmentVariables(command) @@ -482,7 +483,8 @@ func prepareCommandWithoutTTY(command string, privilegedExecution bool) string { } func prepareCommandTTY(command string, currentNanoTime int64, privilegedExecution bool) string { - command = setInnerDebugMessages(command, false) + const commandFieldAfterSettingEnvVariables = 4 + command = setInnerDebugMessages(command, commandFieldAfterSettingEnvVariables, -1) // Take the command to be executed and wrap it to redirect stderr. stderrFifoPath := stderrFifo(currentNanoTime) @@ -496,7 +498,7 @@ func prepareCommandTTY(command string, currentNanoTime int64, privilegedExecutio func prepareCommandTTYStdErr(currentNanoTime int64, privilegedExecution bool) string { stderrFifoPath := stderrFifo(currentNanoTime) command := fmt.Sprintf(stderrFifoCommandFormat, stderrFifoPath, stderrFifoPath, stderrFifoPath) - command = setInnerDebugMessages(command, false) + command = setInnerDebugMessages(command, 0, 1) command = setUserCommand(command, privilegedExecution) return command } @@ -510,7 +512,8 @@ func unsetEnvironmentVariables(command string) string { command = fmt.Sprintf(unsetEnvironmentVariablesFormat, unsetEnvironmentVariablesShell, command) // Debug Message - command = fmt.Sprintf(timeDebugMessageFormatStart, "UnsetEnv", command) + const commandFieldBeforeBash = 2 // e.g. instead of "unset ${!NOMAD_@} && /bin/bash -c [...]" just "unset ${!NOMAD_@}". + command = injectStartDebugMessage(command, 0, commandFieldBeforeBash) return command } @@ -524,18 +527,36 @@ func setUserCommand(command string, privilegedExecution bool) string { } // Debug Message - command = fmt.Sprintf(timeDebugMessageFormatStart, "SetUser", command) + const commandFieldBeforeBash = 2 // e.g. instead of "/sbin/setuser user /bin/bash -c [...]" just "/sbin/setuser user". + command = injectStartDebugMessage(command, 0, commandFieldBeforeBash) return command } +func injectStartDebugMessage(command string, start uint, end int) string { + commandFields := strings.Fields(command) + if start < uint(len(commandFields)) { + commandFields = commandFields[start:] + end -= int(start) + } + if end >= 0 && end < len(commandFields) { + commandFields = commandFields[:end] + } + + description := strings.Join(commandFields, " ") + if strings.HasPrefix(description, "\"") && strings.HasSuffix(description, "\"") { + description = description[1 : len(description)-1] + } + + description = dto.BashEscapeCommand(description) + description = description[1 : len(description)-1] // The most outer quotes are not escaped! + return fmt.Sprintf(timeDebugMessageFormatStart, description, command) +} + // setInnerDebugMessages injects debug commands into the bash command. // The debug messages are parsed by the SentryDebugWriter. -func setInnerDebugMessages(command string, includeCommandInDescription bool) (result string) { - description := "innerCommand" - if includeCommandInDescription { - description += fmt.Sprintf(" %s", command) - } - result = fmt.Sprintf(timeDebugMessageFormatStart, description, command) +func setInnerDebugMessages(command string, descriptionStart uint, descriptionEnd int) (result string) { + result = injectStartDebugMessage(command, descriptionStart, descriptionEnd) + result = strings.TrimSuffix(result, ";") - return fmt.Sprintf(timeDebugMessageFormatEnd, result, "End") + return fmt.Sprintf(timeDebugMessageFormatEnd, result, "exit $ec") } diff --git a/internal/nomad/sentry_debug_writer.go b/internal/nomad/sentry_debug_writer.go index cc262b6..06d4308 100644 --- a/internal/nomad/sentry_debug_writer.go +++ b/internal/nomad/sentry_debug_writer.go @@ -34,6 +34,7 @@ type SentryDebugWriter struct { func NewSentryDebugWriter(target io.Writer, ctx context.Context) *SentryDebugWriter { span := sentry.StartSpan(ctx, "nomad.execute.connect") + span.Description = "/bin/bash -c" return &SentryDebugWriter{ Target: target, Ctx: ctx, diff --git a/pkg/dto/dto.go b/pkg/dto/dto.go index 0854e17..553cb5c 100644 --- a/pkg/dto/dto.go +++ b/pkg/dto/dto.go @@ -41,17 +41,22 @@ func (er *ExecutionRequest) FullCommand() string { return command } -// WrapBashCommand escapes the passed command and wraps it into a new bash command. +// BashEscapeCommand escapes the passed command and surrounds it with double-quotes. // The escaping includes the characters ", \, $, ` (comma-separated) as they are the exceptional characters // that still have a special meaning with double quotes. See the Bash Manual - Chapter Quoting. // We only handle the dollar-character and the backquote because the %q format already escapes the other two. -func WrapBashCommand(command string) string { - command = fmt.Sprintf("/bin/bash -c %q", command) +func BashEscapeCommand(command string) string { + command = fmt.Sprintf("%q", command) command = strings.ReplaceAll(command, "$", "\\$") command = strings.ReplaceAll(command, "`", "\\`") return command } +// WrapBashCommand escapes the passed command and wraps it into a new bash command. +func WrapBashCommand(command string) string { + return fmt.Sprintf("/bin/bash -c %s", BashEscapeCommand(command)) +} + // EnvironmentID is an id of an environment. type EnvironmentID int