Merge pull request 'Add testing' (#4) from test into master
Reviewed-on: #4
This commit is contained in:
commit
495c14afcc
3
Makefile
3
Makefile
|
@ -6,3 +6,6 @@ weblug: cmd/weblug/weblug.go cmd/weblug/config.go
|
|||
|
||||
static: cmd/weblug/weblug.go cmd/weblug/config.go
|
||||
CGO_ENABLED=0 go build -ldflags="-w -s" -o weblug $^
|
||||
|
||||
test: weblug
|
||||
sudo bash -c "cd test && ./blackbox.sh"
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
|
@ -27,8 +26,9 @@ type Hook struct {
|
|||
Background bool `yaml:"background"` // Run in background
|
||||
Concurrency int `yaml:"concurrency"` // Number of allowed concurrent runs
|
||||
concurrentRuns int32 // Number of current concurrent runs
|
||||
UID int `yaml:"uid"` // UID to use when running the command
|
||||
GID int `yaml:"gid"` // GID to use when running the command
|
||||
UID int `yaml:"uid"` // UID to use when running the command
|
||||
GID int `yaml:"gid"` // GID to use when running the command
|
||||
Output bool `yaml:"output"` // Print program output
|
||||
}
|
||||
|
||||
func (cf *Config) SetDefaults() {
|
||||
|
@ -83,22 +83,56 @@ func (hook *Hook) Unlock() {
|
|||
atomic.AddInt32(&hook.concurrentRuns, -1)
|
||||
}
|
||||
|
||||
// Split a command into program arguments, obey quotation mark escapes
|
||||
func cmdSplit(command string) []string {
|
||||
null := rune(0)
|
||||
esc := null // Escape character or \0 if not escaped currently
|
||||
ret := make([]string, 0)
|
||||
buf := "" // Current command
|
||||
|
||||
for _, char := range command {
|
||||
if esc != null {
|
||||
if char == esc {
|
||||
esc = null
|
||||
} else {
|
||||
buf += string(char)
|
||||
}
|
||||
} else {
|
||||
// Check for quotation marks
|
||||
if char == '\'' || char == '"' {
|
||||
esc = char
|
||||
} else if char == ' ' {
|
||||
ret = append(ret, buf)
|
||||
buf = ""
|
||||
} else {
|
||||
buf += string(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remaining characters
|
||||
if buf != "" {
|
||||
ret = append(ret, buf)
|
||||
buf = ""
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Run executes the given command and return it's return code. It also respects the given concurrency number and will block until resources are free
|
||||
func (hook *Hook) Run() error {
|
||||
split := strings.Split(hook.Command, " ")
|
||||
split := cmdSplit(hook.Command)
|
||||
args := make([]string, 0)
|
||||
if len(split) > 1 {
|
||||
args = split[1:]
|
||||
}
|
||||
cmd := exec.Command(split[0], args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{}
|
||||
if hook.UID > 0 {
|
||||
cmd.SysProcAttr.Credential.Uid = uint32(hook.UID)
|
||||
if hook.UID > 0 || hook.GID > 0 {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(hook.UID), Gid: uint32(hook.GID)}
|
||||
}
|
||||
if hook.GID > 0 {
|
||||
cmd.SysProcAttr.Credential.Gid = uint32(hook.GID)
|
||||
if hook.Output {
|
||||
buf, ret := cmd.Output()
|
||||
fmt.Println(string(buf))
|
||||
return ret
|
||||
}
|
||||
ret := cmd.Run()
|
||||
return ret
|
||||
return cmd.Run()
|
||||
}
|
||||
|
|
|
@ -36,6 +36,21 @@ func awaitTerminationSignal() {
|
|||
}()
|
||||
}
|
||||
|
||||
// Perform sanity check on hooks
|
||||
func sanityCheckHooks(hooks []Hook) error {
|
||||
uid := os.Getuid()
|
||||
for _, hook := range hooks {
|
||||
// If a hook sets a custom uid or gid, ensure we're running as root, otherwise print a warning
|
||||
if hook.UID != 0 && uid != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Hook '%s' sets 'uid = %d' but we're not running as root\n", hook.Name, hook.UID)
|
||||
}
|
||||
if hook.GID != 0 && uid != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: Hook '%s' sets 'gid = %d' but we're not running as root\n", hook.Name, hook.GID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cf.SetDefaults()
|
||||
if len(os.Args) < 2 {
|
||||
|
@ -60,6 +75,12 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if err := sanityCheckHooks(cf.Hooks); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "hook sanity check failed: %s\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
// Create default handlers
|
||||
http.HandleFunc("/health", createHealthHandler())
|
||||
http.HandleFunc("/health.json", createHealthHandler())
|
||||
|
|
59
test/blackbox.sh
Executable file
59
test/blackbox.sh
Executable file
|
@ -0,0 +1,59 @@
|
|||
#!/bin/bash
|
||||
# Blackbox tests for weblug
|
||||
|
||||
cleanup() {
|
||||
# kill all processes whose parent is this process
|
||||
pkill -P $$
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
|
||||
if [[ $EUID != 0 && $UID != 0 ]]; then
|
||||
echo -e "(!!) WARNING: Cannot UID and GID webhook tests, because we're not running as root (!!)\n"
|
||||
echo -e "Continuing in 3 seconds\n\n\n"
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
|
||||
rm -f testfile
|
||||
../weblug test.yaml &
|
||||
sleep 1
|
||||
|
||||
## Check touch webhook, which creates "testfile"
|
||||
|
||||
echo -e "\n\nChecking 'testfile' webhook ... "
|
||||
|
||||
curl http://127.0.0.1:2088/webhooks/touch
|
||||
if [[ ! -f testfile ]]; then
|
||||
echo "Testfile doesn't exist after running webhook touch"
|
||||
exit 1
|
||||
fi
|
||||
rm -f testfile
|
||||
|
||||
## Check background webhook, that sleeps for 5 seconds but returns immediately
|
||||
|
||||
echo -e "\n\nChecking 'background' webhook ... "
|
||||
|
||||
timeout 2 curl http://127.0.0.1:2088/webhooks/background
|
||||
|
||||
## Check concurrency webhook, that allows only 2 requests at the same time (but sleeps for 5 seconds)
|
||||
|
||||
echo -e "\n\nChecking 'concurrency' webhook ... "
|
||||
|
||||
timeout 10 curl http://127.0.0.1:2088/3 &
|
||||
timeout 10 curl http://127.0.0.1:2088/3 &
|
||||
! timeout 2 curl http://127.0.0.1:2088/3
|
||||
|
||||
## Check UID and GID webhooks, but only if we're root
|
||||
|
||||
echo -e "\n\nChecking 'uid/gid' webhook ... "
|
||||
|
||||
# Skip this test, if we're not root
|
||||
if [[ $EUID == 0 || $UID == 0 ]]; then
|
||||
curl http://127.0.0.1:2088/webhooks/uid
|
||||
curl http://127.0.0.1:2088/webhooks/gid
|
||||
else
|
||||
echo "Cannot UID and GID webhook tests, because we're not running as root"
|
||||
fi
|
||||
|
||||
echo -e "\n\nall good"
|
27
test/test.yaml
Normal file
27
test/test.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
settings:
|
||||
bind: "127.0.0.1:2088" # bind address for webserver
|
||||
|
||||
hooks:
|
||||
- name: 'touch hook'
|
||||
route: "/webhooks/touch"
|
||||
command: "touch testfile"
|
||||
- name: 'hook background'
|
||||
route: "/webhooks/background"
|
||||
command: "sleep 5"
|
||||
background: True
|
||||
- name: 'hook three'
|
||||
route: "/3"
|
||||
command: "sleep 5"
|
||||
concurrency: 2
|
||||
- name: 'hook uid'
|
||||
route: "/webhooks/uid"
|
||||
command: "bash -c 'echo uid=$UID gid=$GID; if [[ $UID != 10 ]]; then exit 1; fi'"
|
||||
uid: 10
|
||||
output: True
|
||||
- name: 'hook gid'
|
||||
route: "/webhooks/gid"
|
||||
command: "bash -c 'GID=`id -g`; echo uid=$UID gid=$GID; if [[ $GID != 10 ]]; then exit 1; fi'"
|
||||
uid: 10
|
||||
gid: 10
|
||||
output: True
|
|
@ -15,11 +15,12 @@ hooks:
|
|||
concurrency: 2 # At most 2 parallel processes are allowed
|
||||
- name: 'hook two'
|
||||
route: "/webhooks/2"
|
||||
command: "sleep 5"
|
||||
command: "bash -c 'sleep 5'"
|
||||
concurrency: 5 # At most 5 parallel processes are allowed
|
||||
- name: 'hook 3'
|
||||
route: "/webhooks/data/3"
|
||||
command: "/srv/fetch-new-data.sh"
|
||||
command: "bash -c 'echo $UID $GID'"
|
||||
uid: 100 # Run command as system user id (uid) 100
|
||||
gid: 200 # Run command with system group id (gid) 200
|
||||
concurrency: 1 # No concurrency. Returns 500 on parallel requests
|
||||
output: True # Print program output to console
|
||||
|
|
Loading…
Reference in a new issue