Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6176,9 +6176,14 @@ func testCmdLabels(t *testing.T, suite *integrationTestSuite) {
{
desc: "Both",
// Print slowly so we can confirm that the output isn't interleaved.
command: slowPrintCommand("abcd1234"),
labels: map[string]string{"spam": "eggs"},
expectLines: []string{"[server-01] abcd1234", "[server-02] abcd1234"},
command: slowPrintCommand("abcd1234"),
labels: map[string]string{"spam": "eggs"},
expectLines: []string{
"Running command on server-01:",
"Running command on server-02:",
"[server-01] abcd1234",
"[server-02] abcd1234",
},
},
{
desc: "Worker only",
Expand All @@ -6202,10 +6207,10 @@ func testCmdLabels(t *testing.T, suite *integrationTestSuite) {
Labels: tt.labels,
}

output, err := runCommand(t, teleport, tt.command, cfg, 1)
output, err := runCommand(t, teleport, tt.command, cfg, 3)
require.NoError(t, err)
outputLines := strings.Split(strings.TrimSpace(output), "\n")
require.Len(t, outputLines, len(tt.expectLines))
require.Len(t, outputLines, len(tt.expectLines), "raw output:\n%v", output)
for _, line := range tt.expectLines {
require.Contains(t, outputLines, line)
}
Expand Down
21 changes: 14 additions & 7 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,7 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, clt *ClusterCli
)
defer span.End()

displayName := nodeName(node)
nodeClient, err := tc.ConnectToNode(
ctx,
clt,
Expand All @@ -2905,26 +2906,32 @@ func (tc *TeleportClient) runCommandOnNodes(ctx context.Context, clt *ClusterCli
if err != nil {
// Returning the error here would cancel all the other goroutines, so
// print the error instead to let them all finish.
fmt.Fprintln(tc.Stderr, err)
fmt.Fprintln(stderr, err)
resultsCh <- execResult{
hostname: displayName,
exitStatus: 1,
}
return nil
}
defer nodeClient.Close()

displayName := nodeName(node)
fmt.Printf("Running command on %v:\n", displayName)
fmt.Fprintf(stdout, "Running command on %v:\n", displayName)

if err := nodeClient.RunCommand(
err = nodeClient.RunCommand(
ctx,
command,
WithLabeledOutput(width),
WithOutput(stdout, stderr),
); err != nil && tc.ExitStatus == 0 {
fmt.Fprintln(tc.Stderr, err)
)
// Use the status from the error to avoid a race on tc.ExitStatus.
exitStatus := getExitStatus(err)
if err != nil && exitStatus == 0 {
fmt.Fprintln(stderr, err)
return nil
}
resultsCh <- execResult{
hostname: displayName,
exitStatus: tc.ExitStatus,
exitStatus: exitStatus,
}
return nil
})
Expand Down
36 changes: 20 additions & 16 deletions lib/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,24 +614,28 @@ func (c *NodeClient) RunCommand(ctx context.Context, command []string, opts ...R
return trace.Wrap(err)
}
defer nodeSession.Close()
if err := nodeSession.runCommand(ctx, types.SessionPeerMode, command, c.TC.OnChannelRequest, c.TC.OnShellCreated, c.TC.Config.InteractiveCommand); err != nil {
originErr := trace.Unwrap(err)
var exitErr *ssh.ExitError
if errors.As(originErr, &exitErr) {
c.TC.ExitStatus = exitErr.ExitStatus()
} else {
// if an error occurs, but no exit status is passed back, GoSSH returns
// a generic error like this. in this case the error message is printed
// to stderr by the remote process so we have to quietly return 1:
if strings.Contains(originErr.Error(), "exited without exit status") {
c.TC.ExitStatus = 1
}
}
err = nodeSession.runCommand(ctx, types.SessionPeerMode, command, c.TC.OnChannelRequest, c.TC.OnShellCreated, c.TC.Config.InteractiveCommand)
c.TC.ExitStatus = getExitStatus(err)
return trace.Wrap(err)
}

return trace.Wrap(err)
func getExitStatus(err error) int {
if err == nil {
return 0
}

return nil
originErr := trace.Unwrap(err)
var exitErr *ssh.ExitError
if errors.As(originErr, &exitErr) {
return exitErr.ExitStatus()
} else {
// if an error occurs, but no exit status is passed back, GoSSH returns
// a generic error like this. in this case the error message is printed
// to stderr by the remote process so we have to quietly return 1:
if strings.Contains(originErr.Error(), "exited without exit status") {
return 1
}
}
return 0
}

// AddEnv add environment variable to SSH session. This method needs to be called
Expand Down
Loading