diff --git a/README.md b/README.md index e119610..2d8cfdf 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ All configuration variables are described in [etc/default/webhookd.env](./etc/de Webhooks are simple scripts within a directory structure. 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:* @@ -87,7 +87,8 @@ In particular, examples of integration with Gitlab and Github. 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. The streaming technology depends on the HTTP request: diff --git a/pkg/api/index.go b/pkg/api/index.go index 6722594..9b1115b 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -18,6 +18,7 @@ import ( var ( defaultTimeout int + defaultExt string scriptDir string outputDir string ) @@ -32,6 +33,7 @@ func atoiFallback(str string, fallback int) int { // index is the main handler of the API. func index(conf *config.Config) http.Handler { defaultTimeout = conf.HookTimeout + defaultExt = conf.HookDefaultExt scriptDir = conf.ScriptDir outputDir = conf.HookLogDir return http.HandlerFunc(webhookHandler) @@ -61,7 +63,7 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) { infoHandler(w, r) return } - _, err := hook.ResolveScript(scriptDir, hookName) + script, err := hook.ResolveScript(scriptDir, hookName, defaultExt) if err != nil { slog.Error("hooke not found", "err", err.Error()) 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 = append(params, HTTPParamsToShellVars(r.Header)...) - // Create work + // Create hook job timeout := atoiFallback(r.Header.Get("X-Hook-Timeout"), defaultTimeout) job, err := hook.NewHookJob(&hook.Request{ Name: hookName, + Script: script, Method: r.Method, Payload: string(body), Args: params, Timeout: timeout, - BaseDir: scriptDir, OutputDir: outputDir, }) if err != nil { @@ -146,7 +148,7 @@ func getWebhookLog(w http.ResponseWriter, r *http.Request) { // Get script location hookName := path.Dir(strings.TrimPrefix(r.URL.Path, "/")) - _, err := hook.ResolveScript(scriptDir, hookName) + _, err := hook.ResolveScript(scriptDir, hookName, defaultExt) if err != nil { slog.Error(err.Error()) http.Error(w, err.Error(), http.StatusNotFound) diff --git a/pkg/config/config.go b/pkg/config/config.go index 660d3ff..ad3623c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -13,6 +13,7 @@ type Config struct { 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"` 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"` HookLogDir string `flag:"hook-log-dir" desc:"Hook execution logs location" default:""` ScriptDir string `flag:"scripts" desc:"Scripts location" default:"scripts"` diff --git a/pkg/hook/helper.go b/pkg/hook/helper.go index b783747..6b387d2 100644 --- a/pkg/hook/helper.go +++ b/pkg/hook/helper.go @@ -8,9 +8,9 @@ import ( ) // 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) == "" { - name += ".sh" + name += "." + defaultExt } script := path.Clean(path.Join(dir, name)) if !strings.HasPrefix(script, dir) { diff --git a/pkg/hook/job.go b/pkg/hook/job.go index dc528a1..10b39e0 100644 --- a/pkg/hook/job.go +++ b/pkg/hook/job.go @@ -41,14 +41,10 @@ type Job struct { // NewHookJob creates new hook job func NewHookJob(request *Request) (*Job, error) { - script, err := ResolveScript(request.BaseDir, request.Name) - if err != nil { - return nil, err - } job := &Job{ id: atomic.AddUint64(&hookID, 1), name: request.Name, - script: script, + script: request.Script, method: request.Method, payload: request.Payload, args: request.Args, diff --git a/pkg/hook/test/helper_test.go b/pkg/hook/test/helper_test.go index b4ebf89..1e318c3 100644 --- a/pkg/hook/test/helper_test.go +++ b/pkg/hook/test/helper_test.go @@ -8,25 +8,25 @@ import ( ) 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.Equal(t, "../../../scripts/echo.sh", script, "") } func TestNotResolveScript(t *testing.T) { - _, err := hook.ResolveScript("../../scripts", "foo") + _, err := hook.ResolveScript("../../scripts", "foo", "sh") assert.NotNil(t, err, "") assert.Equal(t, "Script not found: ../../scripts/foo.sh", err.Error(), "") } 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.Equal(t, "Invalid script path: ../tests/test_simple.sh", err.Error(), "") } func TestResolveScriptWithExtension(t *testing.T) { - _, err := hook.ResolveScript("../../scripts", "node.js") + _, err := hook.ResolveScript("../../scripts", "node.js", "sh") assert.NotNil(t, err, "") assert.Equal(t, "Script not found: ../../scripts/node.js", err.Error(), "") } diff --git a/pkg/hook/test/job_test.go b/pkg/hook/test/job_test.go index 6b459df..d7882e1 100644 --- a/pkg/hook/test/job_test.go +++ b/pkg/hook/test/job_test.go @@ -25,6 +25,7 @@ func printJobMessages(job *hook.Job) { func TestHookJob(t *testing.T) { req := &hook.Request{ Name: "test_simple", + Script: "../test/test_simple.sh", Method: "GET", Payload: "{\"foo\": \"bar\"}", Args: []string{ @@ -32,7 +33,6 @@ func TestHookJob(t *testing.T) { "user_agent=test", }, Timeout: 5, - BaseDir: "../test", OutputDir: os.TempDir(), } job, err := hook.NewHookJob(req) @@ -55,11 +55,11 @@ func TestHookJob(t *testing.T) { func TestWorkRunnerWithError(t *testing.T) { req := &hook.Request{ Name: "test_error", + Script: "../test/test_error.sh", Method: "POST", Payload: "", Args: []string{}, Timeout: 5, - BaseDir: "../test", OutputDir: os.TempDir(), } job, err := hook.NewHookJob(req) @@ -75,11 +75,11 @@ func TestWorkRunnerWithError(t *testing.T) { func TestWorkRunnerWithTimeout(t *testing.T) { req := &hook.Request{ Name: "test_timeout", + Script: "../test/test_timeout.sh", Method: "POST", Payload: "", Args: []string{}, Timeout: 1, - BaseDir: "../test", OutputDir: os.TempDir(), } job, err := hook.NewHookJob(req) diff --git a/pkg/hook/types.go b/pkg/hook/types.go index 4adb64d..0c5645f 100644 --- a/pkg/hook/types.go +++ b/pkg/hook/types.go @@ -17,10 +17,10 @@ const ( // Request is a hook request type Request struct { Name string + Script string Method string Payload string Args []string Timeout int - BaseDir string OutputDir string }