feat(hook): add default extension parameter

This commit is contained in:
Nicolas Carlier 2023-12-22 22:10:19 +00:00
parent bc846f48b4
commit 8ea426222f
8 changed files with 21 additions and 21 deletions

View File

@ -63,7 +63,7 @@ All configuration variables are described in [etc/default/webhookd.env](./etc/de
Webhooks are simple scripts within a directory structure. Webhooks are simple scripts within a directory structure.
By default inside the `./scripts` directory. By default inside the `./scripts` directory.
You can override the default using the `WHD_SCRIPTS` environment variable or `-script` parameter. You can change the default directory using the `WHD_SCRIPTS` environment variable or `-script` parameter.
*Example:* *Example:*
@ -87,7 +87,8 @@ In particular, examples of integration with Gitlab and Github.
The directory structure define the webhook URL. The directory structure define the webhook URL.
You can omit the script extension. If you do, webhookd will search for a `.sh` file. You can omit the script extension. If you do, webhookd will search by default for a `.sh` file.
You can change the default extension using the `WHD_HOOK_DEFAULT_EXT` environment variable or `-hook-default-ext` parameter.
If the script exists, the output the will be streamed to the HTTP response. If the script exists, the output the will be streamed to the HTTP response.
The streaming technology depends on the HTTP request: The streaming technology depends on the HTTP request:

View File

@ -18,6 +18,7 @@ import (
var ( var (
defaultTimeout int defaultTimeout int
defaultExt string
scriptDir string scriptDir string
outputDir string outputDir string
) )
@ -32,6 +33,7 @@ func atoiFallback(str string, fallback int) int {
// index is the main handler of the API. // index is the main handler of the API.
func index(conf *config.Config) http.Handler { func index(conf *config.Config) http.Handler {
defaultTimeout = conf.HookTimeout defaultTimeout = conf.HookTimeout
defaultExt = conf.HookDefaultExt
scriptDir = conf.ScriptDir scriptDir = conf.ScriptDir
outputDir = conf.HookLogDir outputDir = conf.HookLogDir
return http.HandlerFunc(webhookHandler) return http.HandlerFunc(webhookHandler)
@ -61,7 +63,7 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
infoHandler(w, r) infoHandler(w, r)
return return
} }
_, err := hook.ResolveScript(scriptDir, hookName) script, err := hook.ResolveScript(scriptDir, hookName, defaultExt)
if err != nil { if err != nil {
slog.Error("hooke not found", "err", err.Error()) slog.Error("hooke not found", "err", err.Error())
http.Error(w, "hook not found", http.StatusNotFound) http.Error(w, "hook not found", http.StatusNotFound)
@ -92,15 +94,15 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
params := HTTPParamsToShellVars(r.Form) params := HTTPParamsToShellVars(r.Form)
params = append(params, HTTPParamsToShellVars(r.Header)...) params = append(params, HTTPParamsToShellVars(r.Header)...)
// Create work // Create hook job
timeout := atoiFallback(r.Header.Get("X-Hook-Timeout"), defaultTimeout) timeout := atoiFallback(r.Header.Get("X-Hook-Timeout"), defaultTimeout)
job, err := hook.NewHookJob(&hook.Request{ job, err := hook.NewHookJob(&hook.Request{
Name: hookName, Name: hookName,
Script: script,
Method: r.Method, Method: r.Method,
Payload: string(body), Payload: string(body),
Args: params, Args: params,
Timeout: timeout, Timeout: timeout,
BaseDir: scriptDir,
OutputDir: outputDir, OutputDir: outputDir,
}) })
if err != nil { if err != nil {
@ -146,7 +148,7 @@ func getWebhookLog(w http.ResponseWriter, r *http.Request) {
// Get script location // Get script location
hookName := path.Dir(strings.TrimPrefix(r.URL.Path, "/")) hookName := path.Dir(strings.TrimPrefix(r.URL.Path, "/"))
_, err := hook.ResolveScript(scriptDir, hookName) _, err := hook.ResolveScript(scriptDir, hookName, defaultExt)
if err != nil { if err != nil {
slog.Error(err.Error()) slog.Error(err.Error())
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)

View File

@ -13,6 +13,7 @@ type Config struct {
TLSKeyFile string `flag:"tls-key-file" desc:"TLS key file" default:"server.key"` TLSKeyFile string `flag:"tls-key-file" desc:"TLS key file" default:"server.key"`
TLSDomain string `flag:"tls-domain" desc:"TLS domain name used by ACME"` TLSDomain string `flag:"tls-domain" desc:"TLS domain name used by ACME"`
NbWorkers int `flag:"nb-workers" desc:"Number of workers to start" default:"2"` NbWorkers int `flag:"nb-workers" desc:"Number of workers to start" default:"2"`
HookDefaultExt string `flag:"hook-default-ext" desc:"Default extension for hook scripts" default:"sh"`
HookTimeout int `flag:"hook-timeout" desc:"Maximum hook execution time in second" default:"10"` HookTimeout int `flag:"hook-timeout" desc:"Maximum hook execution time in second" default:"10"`
HookLogDir string `flag:"hook-log-dir" desc:"Hook execution logs location" default:""` HookLogDir string `flag:"hook-log-dir" desc:"Hook execution logs location" default:""`
ScriptDir string `flag:"scripts" desc:"Scripts location" default:"scripts"` ScriptDir string `flag:"scripts" desc:"Scripts location" default:"scripts"`

View File

@ -8,9 +8,9 @@ import (
) )
// ResolveScript is resolving the target script. // ResolveScript is resolving the target script.
func ResolveScript(dir, name string) (string, error) { func ResolveScript(dir, name, defaultExt string) (string, error) {
if path.Ext(name) == "" { if path.Ext(name) == "" {
name += ".sh" name += "." + defaultExt
} }
script := path.Clean(path.Join(dir, name)) script := path.Clean(path.Join(dir, name))
if !strings.HasPrefix(script, dir) { if !strings.HasPrefix(script, dir) {

View File

@ -41,14 +41,10 @@ type Job struct {
// NewHookJob creates new hook job // NewHookJob creates new hook job
func NewHookJob(request *Request) (*Job, error) { func NewHookJob(request *Request) (*Job, error) {
script, err := ResolveScript(request.BaseDir, request.Name)
if err != nil {
return nil, err
}
job := &Job{ job := &Job{
id: atomic.AddUint64(&hookID, 1), id: atomic.AddUint64(&hookID, 1),
name: request.Name, name: request.Name,
script: script, script: request.Script,
method: request.Method, method: request.Method,
payload: request.Payload, payload: request.Payload,
args: request.Args, args: request.Args,

View File

@ -8,25 +8,25 @@ import (
) )
func TestResolveScript(t *testing.T) { func TestResolveScript(t *testing.T) {
script, err := hook.ResolveScript("../../../scripts", "../scripts/echo") script, err := hook.ResolveScript("../../../scripts", "../scripts/echo", "sh")
assert.Nil(t, err, "") assert.Nil(t, err, "")
assert.Equal(t, "../../../scripts/echo.sh", script, "") assert.Equal(t, "../../../scripts/echo.sh", script, "")
} }
func TestNotResolveScript(t *testing.T) { func TestNotResolveScript(t *testing.T) {
_, err := hook.ResolveScript("../../scripts", "foo") _, err := hook.ResolveScript("../../scripts", "foo", "sh")
assert.NotNil(t, err, "") assert.NotNil(t, err, "")
assert.Equal(t, "Script not found: ../../scripts/foo.sh", err.Error(), "") assert.Equal(t, "Script not found: ../../scripts/foo.sh", err.Error(), "")
} }
func TestResolveBadScript(t *testing.T) { func TestResolveBadScript(t *testing.T) {
_, err := hook.ResolveScript("../../scripts", "../tests/test_simple") _, err := hook.ResolveScript("../../scripts", "../tests/test_simple", "sh")
assert.NotNil(t, err, "") assert.NotNil(t, err, "")
assert.Equal(t, "Invalid script path: ../tests/test_simple.sh", err.Error(), "") assert.Equal(t, "Invalid script path: ../tests/test_simple.sh", err.Error(), "")
} }
func TestResolveScriptWithExtension(t *testing.T) { func TestResolveScriptWithExtension(t *testing.T) {
_, err := hook.ResolveScript("../../scripts", "node.js") _, err := hook.ResolveScript("../../scripts", "node.js", "sh")
assert.NotNil(t, err, "") assert.NotNil(t, err, "")
assert.Equal(t, "Script not found: ../../scripts/node.js", err.Error(), "") assert.Equal(t, "Script not found: ../../scripts/node.js", err.Error(), "")
} }

View File

@ -25,6 +25,7 @@ func printJobMessages(job *hook.Job) {
func TestHookJob(t *testing.T) { func TestHookJob(t *testing.T) {
req := &hook.Request{ req := &hook.Request{
Name: "test_simple", Name: "test_simple",
Script: "../test/test_simple.sh",
Method: "GET", Method: "GET",
Payload: "{\"foo\": \"bar\"}", Payload: "{\"foo\": \"bar\"}",
Args: []string{ Args: []string{
@ -32,7 +33,6 @@ func TestHookJob(t *testing.T) {
"user_agent=test", "user_agent=test",
}, },
Timeout: 5, Timeout: 5,
BaseDir: "../test",
OutputDir: os.TempDir(), OutputDir: os.TempDir(),
} }
job, err := hook.NewHookJob(req) job, err := hook.NewHookJob(req)
@ -55,11 +55,11 @@ func TestHookJob(t *testing.T) {
func TestWorkRunnerWithError(t *testing.T) { func TestWorkRunnerWithError(t *testing.T) {
req := &hook.Request{ req := &hook.Request{
Name: "test_error", Name: "test_error",
Script: "../test/test_error.sh",
Method: "POST", Method: "POST",
Payload: "", Payload: "",
Args: []string{}, Args: []string{},
Timeout: 5, Timeout: 5,
BaseDir: "../test",
OutputDir: os.TempDir(), OutputDir: os.TempDir(),
} }
job, err := hook.NewHookJob(req) job, err := hook.NewHookJob(req)
@ -75,11 +75,11 @@ func TestWorkRunnerWithError(t *testing.T) {
func TestWorkRunnerWithTimeout(t *testing.T) { func TestWorkRunnerWithTimeout(t *testing.T) {
req := &hook.Request{ req := &hook.Request{
Name: "test_timeout", Name: "test_timeout",
Script: "../test/test_timeout.sh",
Method: "POST", Method: "POST",
Payload: "", Payload: "",
Args: []string{}, Args: []string{},
Timeout: 1, Timeout: 1,
BaseDir: "../test",
OutputDir: os.TempDir(), OutputDir: os.TempDir(),
} }
job, err := hook.NewHookJob(req) job, err := hook.NewHookJob(req)

View File

@ -17,10 +17,10 @@ const (
// Request is a hook request // Request is a hook request
type Request struct { type Request struct {
Name string Name string
Script string
Method string Method string
Payload string Payload string
Args []string Args []string
Timeout int Timeout int
BaseDir string
OutputDir string OutputDir string
} }