feat: add shebang support to scripts (#10134)

This enables much greater portability!
This commit is contained in:
Kyle Carberry 2023-10-09 10:57:57 -05:00 committed by GitHub
parent 17869ecb74
commit b402f2a816
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 63 additions and 1 deletions

View File

@ -19,6 +19,7 @@ import (
"time"
"github.com/gliderlabs/ssh"
"github.com/kballard/go-shellquote"
"github.com/pkg/sftp"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/afero"
@ -515,8 +516,29 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
if runtime.GOOS == "windows" {
caller = "/c"
}
name := shell
args := []string{caller, script}
if strings.HasPrefix(strings.TrimSpace(script), "#!") {
// If the script starts with a shebang, we should
// execute it directly. This is useful for running
// scripts that aren't executable.
shebang := strings.SplitN(script, "\n", 2)[0]
shebang = strings.TrimPrefix(shebang, "#!")
shebang = strings.TrimSpace(shebang)
words, err := shellquote.Split(shebang)
if err != nil {
return nil, xerrors.Errorf("split shebang: %w", err)
}
name = words[0]
if len(words) > 1 {
args = words[1:]
} else {
args = []string{}
}
args = append(args, caller, script)
}
// gliderlabs/ssh returns a command slice of zero
// when a shell is requested.
if len(script) == 0 {
@ -528,7 +550,7 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
}
}
cmd := pty.CommandContext(ctx, shell, args...)
cmd := pty.CommandContext(ctx, name, args...)
cmd.Dir = manifest.Directory
// If the metadata directory doesn't exist, we run the command

View File

@ -6,6 +6,7 @@ import (
"bytes"
"context"
"net"
"runtime"
"strings"
"sync"
"testing"
@ -71,6 +72,42 @@ func TestNewServer_ServeClient(t *testing.T) {
<-done
}
func TestNewServer_ExecuteShebang(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("bash doesn't exist on Windows")
}
ctx := context.Background()
logger := slogtest.Make(t, nil)
s, err := agentssh.NewServer(ctx, logger, prometheus.NewRegistry(), afero.NewMemMapFs(), 0, "")
require.NoError(t, err)
t.Cleanup(func() {
_ = s.Close()
})
s.AgentToken = func() string { return "" }
s.Manifest = atomic.NewPointer(&agentsdk.Manifest{})
t.Run("Basic", func(t *testing.T) {
t.Parallel()
cmd, err := s.CreateCommand(ctx, `#!/bin/bash
echo test`, nil)
require.NoError(t, err)
output, err := cmd.AsExec().CombinedOutput()
require.NoError(t, err)
require.Equal(t, "test\n", string(output))
})
t.Run("Args", func(t *testing.T) {
t.Parallel()
cmd, err := s.CreateCommand(ctx, `#!/usr/bin/env bash
echo test`, nil)
require.NoError(t, err)
output, err := cmd.AsExec().CombinedOutput()
require.NoError(t, err)
require.Equal(t, "test\n", string(output))
})
}
func TestNewServer_CloseActiveConnections(t *testing.T) {
t.Parallel()

View File

@ -172,6 +172,7 @@ resource "coder_agent" "dev" {
display_name = "Swap Usage (Host)"
key = "4_swap_usage_host"
script = <<EOT
#!/bin/bash
echo "$(free -b | awk '/^Swap/ { printf("%.1f/%.1f", $3/1024.0/1024.0/1024.0, $2/1024.0/1024.0/1024.0) }') GiB"
EOT
interval = 10
@ -183,6 +184,7 @@ resource "coder_agent" "dev" {
key = "5_load_host"
# get load avg scaled by number of cores
script = <<EOT
#!/bin/bash
echo "`cat /proc/loadavg | awk '{ print $1 }'` `nproc`" | awk '{ printf "%0.2f", $1/$2 }'
EOT
interval = 60
@ -201,6 +203,7 @@ resource "coder_agent" "dev" {
display_name = "Word of the Day"
key = "7_word"
script = <<EOT
#!/bin/bash
curl -o - --silent https://www.merriam-webster.com/word-of-the-day 2>&1 | awk ' $0 ~ "Word of the Day: [A-z]+" { print $5; exit }'
EOT
interval = 86400