mirror of
https://github.com/ncarlier/webhookd.git
synced 2025-04-20 19:27:06 +00:00
feat(): ACME support + configuration refactoring
This commit is contained in:
parent
908232f2fa
commit
c7ea370de1
78
README.md
78
README.md
|
@ -38,44 +38,20 @@ $ docker run -d --name=webhookd \
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
You can configure the daemon by:
|
Webhookd can be configured by using command line parameters or by setting environment variables.
|
||||||
|
|
||||||
### Setting environment variables:
|
Type `webhookd -h` to display all parameters and related environment variables.
|
||||||
|
|
||||||
| Variable | Default | Description |
|
All configuration variables are described in [etc/default/webhookd.env](./etc/default/webhookd.env) file.
|
||||||
|----------|---------|-------------|
|
|
||||||
| `APP_LISTEN_ADDR` | `:8080` | HTTP service address |
|
|
||||||
| `APP_PASSWD_FILE` | `.htpasswd` | Password file for HTTP basic authentication |
|
|
||||||
| `APP_NB_WORKERS` | `2` | The number of workers to start |
|
|
||||||
| `APP_HOOK_TIMEOUT` | `10` | Hook maximum delay before timeout (in second) |
|
|
||||||
| `APP_SCRIPTS_DIR` | `./scripts` | Scripts directory |
|
|
||||||
| `APP_SCRIPTS_GIT_URL` | none | GIT repository that contains scripts (Note: this is only used by the Docker image or by using the Docker entrypoint script) |
|
|
||||||
| `APP_SCRIPTS_GIT_KEY` | none | GIT SSH private key used to clone the repository (Note: this is only used by the Docker image or by using the Docker entrypoint script) |
|
|
||||||
| `APP_LOG_DIR` | `/tmp` (OS temp dir) | Directory to store execution logs |
|
|
||||||
| `APP_NOTIFICATION_URI` | none | Notification configuration URI |
|
|
||||||
| `APP_DEBUG` | `false` | Output debug logs |
|
|
||||||
|
|
||||||
### Using command parameters:
|
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `-l <address> or --listen <address>` | `:8080` | HTTP service address |
|
|
||||||
| `-p or --passwd <htpasswd file>` | `.htpasswd` | Password file for HTTP basic authentication
|
|
||||||
| `-d or --debug` | false | Output debug logs |
|
|
||||||
| `--nb-workers <workers>` | `2` | The number of workers to start |
|
|
||||||
| `--scripts <dir>` | `./scripts` | Scripts directory |
|
|
||||||
| `--timeout <timeout>` | `10` | Hook maximum delay before timeout (in second) |
|
|
||||||
| `--notification-uri <uri>` | | Notification configuration URI |
|
|
||||||
| `--log-dir <dir>` | `/tmp` | Directory to store execution logs |
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Directory structure
|
### Directory structure
|
||||||
|
|
||||||
Webhooks are simple scripts dispatched into 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 `APP_SCRIPTS_DIR` environment variable.
|
You can override the default using the `WHD_SCRIPTS_DIR` environment variable or `-script` parameter.
|
||||||
|
|
||||||
*Example:*
|
*Example:*
|
||||||
|
|
||||||
|
@ -177,7 +153,7 @@ done
|
||||||
|
|
||||||
By default a webhook has a timeout of 10 seconds.
|
By default a webhook has a timeout of 10 seconds.
|
||||||
This timeout is globally configurable by setting the environment variable:
|
This timeout is globally configurable by setting the environment variable:
|
||||||
`APP_HOOK_TIMEOUT` (in seconds).
|
`WHD_HOOK_TIMEOUT` (in seconds).
|
||||||
|
|
||||||
You can override this global behavior per request by setting the HTTP header:
|
You can override this global behavior per request by setting the HTTP header:
|
||||||
`X-Hook-Timeout` (in seconds).
|
`X-Hook-Timeout` (in seconds).
|
||||||
|
@ -212,7 +188,7 @@ $ curl http://localhost:8080/echo/2
|
||||||
### Post hook notifications
|
### Post hook notifications
|
||||||
|
|
||||||
The output of the script is collected and stored into a log file
|
The output of the script is collected and stored into a log file
|
||||||
(configured by the `APP_LOG_DIR` environment variable).
|
(configured by the `WHD_LOG_DIR` environment variable).
|
||||||
|
|
||||||
Once the script is executed, you can send the result and this log file to a notification channel.
|
Once the script is executed, you can send the result and this log file to a notification channel.
|
||||||
Currently, only two channels are supported: `Email` and `HTTP`.
|
Currently, only two channels are supported: `Email` and `HTTP`.
|
||||||
|
@ -220,7 +196,7 @@ Currently, only two channels are supported: `Email` and `HTTP`.
|
||||||
Notifications configuration can be done as follow:
|
Notifications configuration can be done as follow:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ export APP_NOTIFICATION_URI=http://requestb.in/v9b229v9
|
$ export WHD_NOTIFICATION_URI=http://requestb.in/v9b229v9
|
||||||
$ # or
|
$ # or
|
||||||
$ webhookd --notification-uri=http://requestb.in/v9b229v9
|
$ webhookd --notification-uri=http://requestb.in/v9b229v9
|
||||||
```
|
```
|
||||||
|
@ -237,7 +213,7 @@ echo "notify: Hello World" # Will be notified
|
||||||
echo "Goodbye" # Will not be notified
|
echo "Goodbye" # Will not be notified
|
||||||
```
|
```
|
||||||
|
|
||||||
You can overide the notification prefix by adding `prefix` as a query parameter to the configuration URL.
|
You can override the notification prefix by adding `prefix` as a query parameter to the configuration URL.
|
||||||
|
|
||||||
**Example:** http://requestb.in/v9b229v9?prefix="foo:"
|
**Example:** http://requestb.in/v9b229v9?prefix="foo:"
|
||||||
|
|
||||||
|
@ -291,7 +267,7 @@ Please note that by default, the daemon will try to load the `.htpasswd` file.
|
||||||
But you can override this behavior by specifying the location of the file:
|
But you can override this behavior by specifying the location of the file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ APP_PASSWD_FILE=/etc/webhookd/users.htpasswd
|
$ export WHD_PASSWD_FILE=/etc/webhookd/users.htpasswd
|
||||||
$ # or
|
$ # or
|
||||||
$ webhookd -p /etc/webhookd/users.htpasswd
|
$ webhookd -p /etc/webhookd/users.htpasswd
|
||||||
```
|
```
|
||||||
|
@ -302,6 +278,36 @@ Once configured, you must call webhooks using basic authentication:
|
||||||
$ curl -u api:test -XPOST "http://localhost:8080/echo?msg=hello"
|
$ curl -u api:test -XPOST "http://localhost:8080/echo?msg=hello"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### TLS
|
||||||
|
|
||||||
|
You can activate TLS to secure communications:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ export WHD_TLS_LISTEN_ADDR=:8443
|
||||||
|
$ # or
|
||||||
|
$ webhookd -tls-listen-addr=:8443
|
||||||
|
```
|
||||||
|
|
||||||
|
This will disable HTTP port.
|
||||||
|
|
||||||
|
By default webhookd is expecting a certificate and key file (`./server.pem` and `./server.key`).
|
||||||
|
You can provide your own certificate and key with `-tls-cert-file` and `-tls-key-file`.
|
||||||
|
|
||||||
|
Webhookd also support [ACME](https://ietf-wg-acme.github.io/acme/) protocol.
|
||||||
|
You can activate ACME by setting a fully qualified domain name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ export WHD_TLS_LISTEN_ADDR=:8443
|
||||||
|
$ export WHD_TLS_DOMAIN=hook.example.com
|
||||||
|
$ # or
|
||||||
|
$ webhookd -tls-listen-addr=:8443 -tls-domain=hook.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:**
|
||||||
|
On *nix, if you want to listen on ports 80 and 443, don't forget to use `setcap` to privilege the binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo setcap CAP_NET_BIND_SERVICE+ep webhookd
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
version: "3.5"
|
version: "3.5"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
webhookd:
|
||||||
webhookd:
|
hostname: webhookd
|
||||||
hostname: webhookd
|
image: ncarlier/webhookd:latest
|
||||||
image: ncarlier/webhookd:latest
|
container_name: webhookd
|
||||||
container_name: webhookd
|
restart: always
|
||||||
restart: always
|
ports:
|
||||||
networks:
|
- "8080:8080"
|
||||||
- default
|
environment:
|
||||||
ports:
|
- WHD_SCRIPTS_DIR=/scripts
|
||||||
- "8080:8080"
|
volumes:
|
||||||
environment:
|
- ./scripts:/scripts
|
||||||
- APP_SCRIPTS_DIR=/scripts
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- ~/docker/webhookd/scripts:/scripts
|
|
||||||
- ~/docker/webhookd/logs:/tmp
|
|
||||||
|
|
43
etc/default/webhookd.env
Normal file
43
etc/default/webhookd.env
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
###
|
||||||
|
# Webhookd configuration
|
||||||
|
###
|
||||||
|
|
||||||
|
# Output debug logs, default is false
|
||||||
|
#WHD_DEBUG=false
|
||||||
|
|
||||||
|
# HTTP listen address, default is ":8080"
|
||||||
|
# Example: `localhost:8080` or `:8080` for all interfaces
|
||||||
|
#WHD_LISTEN_ADDR=":8080"
|
||||||
|
|
||||||
|
# Hook execution logs location, default is OS temporary directory
|
||||||
|
#WHD_LOG_DIR="/tmp"
|
||||||
|
|
||||||
|
# Number of workers to start, default is 2
|
||||||
|
#WHD_NB_WORKERS=2
|
||||||
|
|
||||||
|
# Notification URI, disabled by default
|
||||||
|
# Example: `http://requestb.in/v9b229v9` or `mailto://foo@bar.com?smtp=smtp-relay-localnet:25`
|
||||||
|
#WHD_NOTIFICATION_URI=
|
||||||
|
|
||||||
|
# Password file for HTTP basic authentication, default is ".htpasswd"
|
||||||
|
#WHD_PASSWD_FILE=".htpasswd"
|
||||||
|
|
||||||
|
# Scripts location, default is "scripts"
|
||||||
|
#WHD_SCRIPTS="scripts"
|
||||||
|
|
||||||
|
# Maximum hook execution time in second, default is 10
|
||||||
|
#WHD_HOOK_TIMEOUT=10
|
||||||
|
|
||||||
|
# TLS listend address, disabled by default
|
||||||
|
# Example: `localhost:8443` or `:8443` for all interfaces
|
||||||
|
#WHD_TLS_LISTEN_ADDR=
|
||||||
|
|
||||||
|
# TLS key file, default is "./server.key"
|
||||||
|
#WHD_TLS_KEY_FILE="./server.key"
|
||||||
|
|
||||||
|
# TLS certificate file, default is "./server.crt"
|
||||||
|
#WHD_TLS_CERT_FILE="./server.pem"
|
||||||
|
|
||||||
|
# TLS domain name used by ACME, key and cert files are ignored if set
|
||||||
|
# Example: `hook.example.org`
|
||||||
|
#WHD_TLS_DOMAIN=
|
39
main.go
39
main.go
|
@ -13,10 +13,14 @@ import (
|
||||||
"github.com/ncarlier/webhookd/pkg/config"
|
"github.com/ncarlier/webhookd/pkg/config"
|
||||||
"github.com/ncarlier/webhookd/pkg/logger"
|
"github.com/ncarlier/webhookd/pkg/logger"
|
||||||
"github.com/ncarlier/webhookd/pkg/notification"
|
"github.com/ncarlier/webhookd/pkg/notification"
|
||||||
|
"github.com/ncarlier/webhookd/pkg/server"
|
||||||
"github.com/ncarlier/webhookd/pkg/worker"
|
"github.com/ncarlier/webhookd/pkg/worker"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
conf := &config.Config{}
|
||||||
|
config.HydrateFromFlags(conf)
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *version {
|
if *version {
|
||||||
|
@ -24,30 +28,28 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conf := config.Get()
|
|
||||||
|
|
||||||
level := "info"
|
level := "info"
|
||||||
if *conf.Debug {
|
if conf.Debug {
|
||||||
level = "debug"
|
level = "debug"
|
||||||
}
|
}
|
||||||
logger.Init(level)
|
logger.Init(level)
|
||||||
|
|
||||||
logger.Debug.Println("Starting webhookd server...")
|
if conf.LogDir == "" {
|
||||||
|
conf.LogDir = os.TempDir()
|
||||||
server := &http.Server{
|
|
||||||
Addr: *conf.ListenAddr,
|
|
||||||
Handler: api.NewRouter(config.Get()),
|
|
||||||
ErrorLog: logger.Error,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debug.Println("Starting webhookd server...")
|
||||||
|
|
||||||
|
srv := server.NewServer(conf)
|
||||||
|
|
||||||
// Configure notification
|
// Configure notification
|
||||||
if err := notification.Init(*conf.NotificationURI); err != nil {
|
if err := notification.Init(conf.NotificationURI); err != nil {
|
||||||
logger.Error.Fatalf("Unable to create notification channel: %v\n", err)
|
logger.Error.Fatalf("Unable to create notification channel: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the dispatcher.
|
// Start the dispatcher.
|
||||||
logger.Debug.Printf("Starting the dispatcher (%d workers)...\n", *conf.NbWorkers)
|
logger.Debug.Printf("Starting the dispatcher (%d workers)...\n", conf.NbWorkers)
|
||||||
worker.StartDispatcher(*conf.NbWorkers)
|
worker.StartDispatcher(conf.NbWorkers)
|
||||||
|
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
|
@ -61,17 +63,20 @@ func main() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
server.SetKeepAlivesEnabled(false)
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
|
||||||
logger.Error.Fatalf("Could not gracefully shutdown the server: %v\n", err)
|
logger.Error.Fatalf("Could not gracefully shutdown the server: %v\n", err)
|
||||||
}
|
}
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logger.Info.Println("Server is ready to handle requests at", *conf.ListenAddr)
|
addr := conf.ListenAddr
|
||||||
|
if conf.TLSListenAddr != "" {
|
||||||
|
addr = conf.TLSListenAddr
|
||||||
|
}
|
||||||
|
logger.Info.Println("Server is ready to handle requests at", addr)
|
||||||
api.Start()
|
api.Start()
|
||||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
logger.Error.Fatalf("Could not listen on %s: %v\n", *conf.ListenAddr, err)
|
logger.Error.Fatalf("Could not listen on %s : %v\n", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
var (
|
var (
|
||||||
defaultTimeout int
|
defaultTimeout int
|
||||||
scriptDir string
|
scriptDir string
|
||||||
|
outputDir string
|
||||||
)
|
)
|
||||||
|
|
||||||
func atoiFallback(str string, fallback int) int {
|
func atoiFallback(str string, fallback int) int {
|
||||||
|
@ -31,8 +32,9 @@ 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.Timeout
|
defaultTimeout = conf.Timeout
|
||||||
scriptDir = *conf.ScriptDir
|
scriptDir = conf.ScriptDir
|
||||||
|
outputDir = conf.LogDir
|
||||||
return http.HandlerFunc(webhookHandler)
|
return http.HandlerFunc(webhookHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Create work
|
// Create work
|
||||||
timeout := atoiFallback(r.Header.Get("X-Hook-Timeout"), defaultTimeout)
|
timeout := atoiFallback(r.Header.Get("X-Hook-Timeout"), defaultTimeout)
|
||||||
work := model.NewWorkRequest(p, script, string(body), params, timeout)
|
work := model.NewWorkRequest(p, script, string(body), outputDir, params, timeout)
|
||||||
|
|
||||||
// Put work in queue
|
// Put work in queue
|
||||||
worker.WorkQueue <- *work
|
worker.WorkQueue <- *work
|
||||||
|
@ -125,7 +127,7 @@ func getWebhookLog(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve log file
|
// Retrieve log file
|
||||||
logFile, err := worker.RetrieveLogFile(id, name)
|
logFile, err := worker.RetrieveLogFile(id, name, outputDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println(err.Error())
|
logger.Error.Println(err.Error())
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
@ -25,6 +25,9 @@ func NewRouter(conf *config.Config) *http.ServeMux {
|
||||||
handler = route.HandlerFunc(conf)
|
handler = route.HandlerFunc(conf)
|
||||||
handler = middleware.Method(handler, route.Methods)
|
handler = middleware.Method(handler, route.Methods)
|
||||||
handler = middleware.Cors(handler)
|
handler = middleware.Cors(handler)
|
||||||
|
if conf.TLSListenAddr != "" {
|
||||||
|
handler = middleware.HSTS(handler)
|
||||||
|
}
|
||||||
handler = middleware.Logger(handler)
|
handler = middleware.Logger(handler)
|
||||||
handler = middleware.Tracing(nextRequestID)(handler)
|
handler = middleware.Tracing(nextRequestID)(handler)
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ type Authenticator interface {
|
||||||
|
|
||||||
// NewAuthenticator creates new authenticator form the configuration
|
// NewAuthenticator creates new authenticator form the configuration
|
||||||
func NewAuthenticator(conf *config.Config) Authenticator {
|
func NewAuthenticator(conf *config.Config) Authenticator {
|
||||||
authenticator, err := NewHtpasswdFromFile(*conf.PasswdFile)
|
authenticator, err := NewHtpasswdFromFile(conf.PasswdFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug.Printf("unable to load htpasswd file: \"%s\" (%s)\n", *conf.PasswdFile, err)
|
logger.Debug.Printf("unable to load htpasswd file: \"%s\" (%s)\n", conf.PasswdFile, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return authenticator
|
return authenticator
|
||||||
|
|
|
@ -1,69 +1,17 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config contain global configuration
|
// Config contain global configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ListenAddr *string
|
ListenAddr string `flag:"listen-addr" desc:"HTTP listen address" default:":8080"`
|
||||||
NbWorkers *int
|
TLSListenAddr string `flag:"tls-listen-addr" desc:"TLS listen address"`
|
||||||
Debug *bool
|
TLSCertFile string `flag:"tls-cert-file" desc:"TLS certificate file" default:"server.pem"`
|
||||||
Timeout *int
|
TLSKeyFile string `flag:"tls-key-file" desc:"TLS key file" default:"server.key"`
|
||||||
ScriptDir *string
|
TLSDomain string `flag:"tls-domain" desc:"TLS domain name used by ACME"`
|
||||||
PasswdFile *string
|
NbWorkers int `flag:"nb-workers" desc:"Number of workers to start" default:"2"`
|
||||||
LogDir *string
|
Debug bool `flag:"debug" desc:"Output debug logs" default:"false"`
|
||||||
NotificationURI *string
|
Timeout int `flag:"timeout" desc:"Maximum hook execution time in second" default:"10"`
|
||||||
}
|
ScriptDir string `flag:"scripts" desc:"Scripts location" default:"scripts"`
|
||||||
|
PasswdFile string `flag:"passwd-file" desc:"Password file for basic HTTP authentication" defult:".htpasswd"`
|
||||||
var config = &Config{
|
LogDir string `flag:"log-dir" desc:"Hook execution logs location" default:""`
|
||||||
ListenAddr: flag.String("listen", getEnv("LISTEN_ADDR", ":8080"), "HTTP service address"),
|
NotificationURI string `flag:"notification-uri" desc:"Notification URI"`
|
||||||
NbWorkers: flag.Int("nb-workers", getIntEnv("NB_WORKERS", 2), "The number of workers to start"),
|
|
||||||
Debug: flag.Bool("debug", getBoolEnv("DEBUG", false), "Output debug logs"),
|
|
||||||
Timeout: flag.Int("timeout", getIntEnv("HOOK_TIMEOUT", 10), "Hook maximum delay (in second) before timeout"),
|
|
||||||
ScriptDir: flag.String("scripts", getEnv("SCRIPTS_DIR", "scripts"), "Scripts directory"),
|
|
||||||
PasswdFile: flag.String("passwd", getEnv("PASSWD_FILE", ".htpasswd"), "Password file encoded with htpasswd"),
|
|
||||||
LogDir: flag.String("log-dir", getEnv("LOG_DIR", os.TempDir()), "Webhooks execution log directory"),
|
|
||||||
NotificationURI: flag.String("notification-uri", getEnv("NOTIFICATION_URI", ""), "Notification URI"),
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// set shorthand parameters
|
|
||||||
const shorthand = " (shorthand)"
|
|
||||||
usage := flag.Lookup("listen").Usage + shorthand
|
|
||||||
flag.StringVar(config.ListenAddr, "l", *config.ListenAddr, usage)
|
|
||||||
usage = flag.Lookup("debug").Usage + shorthand
|
|
||||||
flag.BoolVar(config.Debug, "d", *config.Debug, usage)
|
|
||||||
usage = flag.Lookup("passwd").Usage + shorthand
|
|
||||||
flag.StringVar(config.PasswdFile, "p", *config.PasswdFile, usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get global configuration
|
|
||||||
func Get() *Config {
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func getEnv(key, fallback string) string {
|
|
||||||
if value, ok := os.LookupEnv("APP_" + key); ok {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIntEnv(key string, fallback int) int {
|
|
||||||
strValue := getEnv(key, strconv.Itoa(fallback))
|
|
||||||
if value, err := strconv.Atoi(strValue); err == nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBoolEnv(key string, fallback bool) bool {
|
|
||||||
strValue := getEnv(key, strconv.FormatBool(fallback))
|
|
||||||
if value, err := strconv.ParseBool(strValue); err == nil {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
return fallback
|
|
||||||
}
|
}
|
||||||
|
|
62
pkg/config/flag-builder.go
Normal file
62
pkg/config/flag-builder.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidSpecification indicates that a specification is of the wrong type.
|
||||||
|
var ErrInvalidSpecification = errors.New("specification must be a struct pointer")
|
||||||
|
|
||||||
|
// HydrateFromFlags hydrate object form flags
|
||||||
|
func HydrateFromFlags(conf interface{}) error {
|
||||||
|
rv := reflect.ValueOf(conf)
|
||||||
|
for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
typ := rv.Type()
|
||||||
|
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
fieldType := typ.Field(i)
|
||||||
|
field := rv.Field(i)
|
||||||
|
|
||||||
|
var key, desc, val string
|
||||||
|
if tag, ok := fieldType.Tag.Lookup("flag"); ok {
|
||||||
|
key = tag
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tag, ok := fieldType.Tag.Lookup("desc"); ok {
|
||||||
|
desc = tag
|
||||||
|
}
|
||||||
|
if tag, ok := fieldType.Tag.Lookup("default"); ok {
|
||||||
|
val = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fieldType.Type.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
field.SetString(val)
|
||||||
|
ptr, _ := field.Addr().Interface().(*string)
|
||||||
|
setFlagEnvString(ptr, key, desc, val)
|
||||||
|
case reflect.Bool:
|
||||||
|
bVal, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field.SetBool(bVal)
|
||||||
|
ptr, _ := field.Addr().Interface().(*bool)
|
||||||
|
setFlagEnvBool(ptr, key, desc, bVal)
|
||||||
|
case reflect.Int:
|
||||||
|
i64Val, err := strconv.ParseInt(val, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iVal := int(i64Val)
|
||||||
|
field.SetInt(i64Val)
|
||||||
|
ptr, _ := field.Addr().Interface().(*int)
|
||||||
|
setFlagEnvInt(ptr, key, desc, iVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
69
pkg/config/helper.go
Normal file
69
pkg/config/helper.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ncarlier/webhookd/pkg/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const envPrefix = "WHD"
|
||||||
|
|
||||||
|
// setFlagEnvString set string value from flag or env with fallback
|
||||||
|
func setFlagEnvString(p *string, key, desc, fallback string) {
|
||||||
|
if val := envValue(key); val != nil {
|
||||||
|
fallback = *val
|
||||||
|
}
|
||||||
|
flag.StringVar(p, key, fallback, envDesc(key, desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlagEnvBool set bool value from flag or env with fallback
|
||||||
|
func setFlagEnvBool(p *bool, key, desc string, fallback bool) {
|
||||||
|
if val := envValue(key); val != nil {
|
||||||
|
fallback, _ = strconv.ParseBool(*val)
|
||||||
|
}
|
||||||
|
flag.BoolVar(p, key, fallback, envDesc(key, desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlagEnvInt set int value from flag or env with fallback
|
||||||
|
func setFlagEnvInt(p *int, key, desc string, fallback int) {
|
||||||
|
if val := envValue(key); val != nil {
|
||||||
|
fallback, _ = strconv.Atoi(*val)
|
||||||
|
}
|
||||||
|
flag.IntVar(p, key, fallback, envDesc(key, desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlagEnvDuration set duration value form flag or env with fallback
|
||||||
|
func setFlagEnvDuration(p *time.Duration, key, desc string, fallback time.Duration) {
|
||||||
|
if val := envValue(key); val != nil {
|
||||||
|
fallback, _ = time.ParseDuration(*val)
|
||||||
|
}
|
||||||
|
flag.DurationVar(p, key, fallback, envDesc(key, desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlagString set string value from flag with fallback
|
||||||
|
func setFlagString(p *string, key, desc, fallback string) {
|
||||||
|
flag.StringVar(p, key, fallback, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setFlagBool set bool value from flag with fallback
|
||||||
|
func setFlagBool(p *bool, key, desc string, fallback bool) {
|
||||||
|
flag.BoolVar(p, key, fallback, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envDesc(key, desc string) string {
|
||||||
|
envKey := strings.ToUpper(strcase.ToSnake(key))
|
||||||
|
return fmt.Sprintf("%s (env: %s_%s)", desc, envPrefix, envKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func envValue(key string) *string {
|
||||||
|
envKey := strings.ToUpper(strcase.ToSnake(key))
|
||||||
|
if value, ok := os.LookupEnv(envPrefix + "_" + envKey); ok {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
21
pkg/config/test/flag-builder_test.go
Normal file
21
pkg/config/test/flag-builder_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ncarlier/webhookd/pkg/assert"
|
||||||
|
"github.com/ncarlier/webhookd/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlagBuilder(t *testing.T) {
|
||||||
|
flag.Parse()
|
||||||
|
conf := &config.Config{}
|
||||||
|
err := config.HydrateFromFlags(conf)
|
||||||
|
assert.Nil(t, err, "error should be nil")
|
||||||
|
assert.Equal(t, ":8080", conf.ListenAddr, "")
|
||||||
|
assert.Equal(t, 2, conf.NbWorkers, "")
|
||||||
|
assert.Equal(t, 10, conf.Timeout, "")
|
||||||
|
assert.Equal(t, "scripts", conf.ScriptDir, "")
|
||||||
|
assert.Equal(t, false, conf.Debug, "")
|
||||||
|
}
|
13
pkg/middleware/hsts.go
Normal file
13
pkg/middleware/hsts.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HSTS is a middleware to enabling HSTS on HTTP requests
|
||||||
|
func HSTS(inner http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Strict-Transport-Security", "max-age=15768000 ; includeSubDomains")
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ncarlier/webhookd/pkg/config"
|
|
||||||
"github.com/ncarlier/webhookd/pkg/logger"
|
"github.com/ncarlier/webhookd/pkg/logger"
|
||||||
"github.com/ncarlier/webhookd/pkg/tools"
|
"github.com/ncarlier/webhookd/pkg/tools"
|
||||||
)
|
)
|
||||||
|
@ -48,7 +47,7 @@ type WorkRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWorkRequest creats new work request
|
// NewWorkRequest creats new work request
|
||||||
func NewWorkRequest(name, script, payload string, args []string, timeout int) *WorkRequest {
|
func NewWorkRequest(name, script, payload, output string, args []string, timeout int) *WorkRequest {
|
||||||
w := &WorkRequest{
|
w := &WorkRequest{
|
||||||
ID: atomic.AddUint64(&workID, 1),
|
ID: atomic.AddUint64(&workID, 1),
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -59,7 +58,7 @@ func NewWorkRequest(name, script, payload string, args []string, timeout int) *W
|
||||||
MessageChan: make(chan []byte),
|
MessageChan: make(chan []byte),
|
||||||
Status: Idle,
|
Status: Idle,
|
||||||
}
|
}
|
||||||
w.LogFilename = path.Join(*config.Get().LogDir, fmt.Sprintf("%s_%d_%s.txt", tools.ToSnakeCase(w.Name), w.ID, time.Now().Format("20060102_1504")))
|
w.LogFilename = path.Join(output, fmt.Sprintf("%s_%d_%s.txt", tools.ToSnakeCase(w.Name), w.ID, time.Now().Format("20060102_1504")))
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
89
pkg/server/server.go
Normal file
89
pkg/server/server.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ncarlier/webhookd/pkg/api"
|
||||||
|
"github.com/ncarlier/webhookd/pkg/config"
|
||||||
|
"github.com/ncarlier/webhookd/pkg/logger"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme/autocert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cacheDir() (dir string) {
|
||||||
|
if u, _ := user.Current(); u != nil {
|
||||||
|
dir = filepath.Join(os.TempDir(), "webhookd-acme-cache-"+u.Username)
|
||||||
|
if err := os.MkdirAll(dir, 0700); err == nil {
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is a HTTP server wrapper used to manage TLS
|
||||||
|
type Server struct {
|
||||||
|
self *http.Server
|
||||||
|
tls bool
|
||||||
|
certFile string
|
||||||
|
keyFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe start HTTP(s) server
|
||||||
|
func (s *Server) ListenAndServe() error {
|
||||||
|
if s.tls {
|
||||||
|
return s.self.ListenAndServeTLS(s.certFile, s.keyFile)
|
||||||
|
} else {
|
||||||
|
return s.self.ListenAndServe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stop HTTP(s) server
|
||||||
|
func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
|
s.self.SetKeepAlivesEnabled(false)
|
||||||
|
return s.self.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer create new HTTP(s) server
|
||||||
|
func NewServer(cfg *config.Config) *Server {
|
||||||
|
server := &Server{}
|
||||||
|
if cfg.TLSListenAddr == "" {
|
||||||
|
// Simple HTTP server
|
||||||
|
server.self = &http.Server{
|
||||||
|
Addr: cfg.ListenAddr,
|
||||||
|
Handler: api.NewRouter(cfg),
|
||||||
|
ErrorLog: logger.Error,
|
||||||
|
}
|
||||||
|
server.tls = false
|
||||||
|
} else {
|
||||||
|
// HTTPs server
|
||||||
|
if cfg.TLSDomain == "" {
|
||||||
|
server.self = &http.Server{
|
||||||
|
Addr: cfg.TLSListenAddr,
|
||||||
|
Handler: api.NewRouter(cfg),
|
||||||
|
ErrorLog: logger.Error,
|
||||||
|
}
|
||||||
|
server.certFile = cfg.TLSCertFile
|
||||||
|
server.keyFile = cfg.TLSKeyFile
|
||||||
|
} else {
|
||||||
|
m := &autocert.Manager{
|
||||||
|
Cache: autocert.DirCache(cacheDir()),
|
||||||
|
Prompt: autocert.AcceptTOS,
|
||||||
|
HostPolicy: autocert.HostWhitelist(cfg.TLSDomain),
|
||||||
|
}
|
||||||
|
server.self = &http.Server{
|
||||||
|
Addr: cfg.TLSListenAddr,
|
||||||
|
Handler: api.NewRouter(cfg),
|
||||||
|
ErrorLog: logger.Error,
|
||||||
|
TLSConfig: m.TLSConfig(),
|
||||||
|
}
|
||||||
|
server.certFile = ""
|
||||||
|
server.keyFile = ""
|
||||||
|
}
|
||||||
|
server.tls = true
|
||||||
|
}
|
||||||
|
return server
|
||||||
|
}
|
94
pkg/strcase/snake.go
Normal file
94
pkg/strcase/snake.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Ian Coleman
|
||||||
|
* Copyright (c) 2018 Ma_124, <github.com/Ma124>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, Subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or Substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package strcase converts strings to snake_case or CamelCase
|
||||||
|
package strcase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToSnake converts a string to snake_case
|
||||||
|
func ToSnake(s string) string {
|
||||||
|
return ToDelimited(s, '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE
|
||||||
|
func ToScreamingSnake(s string) string {
|
||||||
|
return ToScreamingDelimited(s, '_', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToKebab converts a string to kebab-case
|
||||||
|
func ToKebab(s string) string {
|
||||||
|
return ToDelimited(s, '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE
|
||||||
|
func ToScreamingKebab(s string) string {
|
||||||
|
return ToScreamingDelimited(s, '-', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToDelimited converts a string to delimited.snake.case (in this case `del = '.'`)
|
||||||
|
func ToDelimited(s string, del uint8) string {
|
||||||
|
return ToScreamingDelimited(s, del, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE (in this case `del = '.'; screaming = true`) or delimited.snake.case (in this case `del = '.'; screaming = false`)
|
||||||
|
func ToScreamingDelimited(s string, del uint8, screaming bool) string {
|
||||||
|
// s = addWordBoundariesToNumbers(s)
|
||||||
|
s = strings.Trim(s, " ")
|
||||||
|
n := ""
|
||||||
|
for i, v := range s {
|
||||||
|
// treat acronyms as words, eg for JSONData -> JSON is a whole word
|
||||||
|
nextCaseIsChanged := false
|
||||||
|
if i+1 < len(s) {
|
||||||
|
next := s[i+1]
|
||||||
|
if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') {
|
||||||
|
nextCaseIsChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i > 0 && n[len(n)-1] != del && nextCaseIsChanged {
|
||||||
|
// add underscore if next letter case type is changed
|
||||||
|
if v >= 'A' && v <= 'Z' {
|
||||||
|
n += string(del) + string(v)
|
||||||
|
} else if v >= 'a' && v <= 'z' {
|
||||||
|
n += string(v) + string(del)
|
||||||
|
}
|
||||||
|
} else if v == ' ' || v == '_' || v == '-' {
|
||||||
|
// replace spaces/underscores with delimiters
|
||||||
|
n += string(del)
|
||||||
|
} else {
|
||||||
|
n = n + string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if screaming {
|
||||||
|
n = strings.ToUpper(n)
|
||||||
|
} else {
|
||||||
|
n = strings.ToLower(n)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import (
|
||||||
func ResolveScript(dir, name string) (string, error) {
|
func ResolveScript(dir, name string) (string, error) {
|
||||||
script := path.Clean(path.Join(dir, fmt.Sprintf("%s.sh", name)))
|
script := path.Clean(path.Join(dir, fmt.Sprintf("%s.sh", name)))
|
||||||
if !strings.HasPrefix(script, dir) {
|
if !strings.HasPrefix(script, dir) {
|
||||||
return "", errors.New("Invalid script path: " + name)
|
return "", errors.New("Invalid script path: " + script)
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(script); os.IsNotExist(err) {
|
if _, err := os.Stat(script); os.IsNotExist(err) {
|
||||||
return "", errors.New("Script not found: " + script)
|
return "", errors.New("Script not found: " + script)
|
||||||
|
|
|
@ -6,13 +6,12 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/ncarlier/webhookd/pkg/config"
|
|
||||||
"github.com/ncarlier/webhookd/pkg/tools"
|
"github.com/ncarlier/webhookd/pkg/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RetrieveLogFile retrieve work log with its name and id
|
// RetrieveLogFile retrieve work log with its name and id
|
||||||
func RetrieveLogFile(id, name string) (*os.File, error) {
|
func RetrieveLogFile(id, name, base string) (*os.File, error) {
|
||||||
logPattern := path.Join(*config.Get().LogDir, fmt.Sprintf("%s_%s_*.txt", tools.ToSnakeCase(name), id))
|
logPattern := path.Join(base, fmt.Sprintf("%s_%s_*.txt", tools.ToSnakeCase(name), id))
|
||||||
files, err := filepath.Glob(logPattern)
|
files, err := filepath.Glob(logPattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package worker
|
package worker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ func TestWorkRunner(t *testing.T) {
|
||||||
"user_agent=test",
|
"user_agent=test",
|
||||||
}
|
}
|
||||||
payload := "{\"foo\": \"bar\"}"
|
payload := "{\"foo\": \"bar\"}"
|
||||||
work := model.NewWorkRequest("test", script, payload, args, 5)
|
work := model.NewWorkRequest("test", script, payload, os.TempDir(), args, 5)
|
||||||
assert.NotNil(t, work, "")
|
assert.NotNil(t, work, "")
|
||||||
printWorkMessages(work)
|
printWorkMessages(work)
|
||||||
err := run(work)
|
err := run(work)
|
||||||
|
@ -39,7 +40,7 @@ func TestWorkRunner(t *testing.T) {
|
||||||
|
|
||||||
// Test that we can retrieve log file afterward
|
// Test that we can retrieve log file afterward
|
||||||
id := strconv.FormatUint(work.ID, 10)
|
id := strconv.FormatUint(work.ID, 10)
|
||||||
logFile, err := RetrieveLogFile(id, "test")
|
logFile, err := RetrieveLogFile(id, "test", os.TempDir())
|
||||||
defer logFile.Close()
|
defer logFile.Close()
|
||||||
assert.Nil(t, err, "Log file should exists")
|
assert.Nil(t, err, "Log file should exists")
|
||||||
assert.NotNil(t, logFile, "Log file should be retrieve")
|
assert.NotNil(t, logFile, "Log file should be retrieve")
|
||||||
|
@ -48,7 +49,7 @@ func TestWorkRunner(t *testing.T) {
|
||||||
func TestWorkRunnerWithError(t *testing.T) {
|
func TestWorkRunnerWithError(t *testing.T) {
|
||||||
logger.Init("debug")
|
logger.Init("debug")
|
||||||
script := "../../tests/test_error.sh"
|
script := "../../tests/test_error.sh"
|
||||||
work := model.NewWorkRequest("test", script, "", []string{}, 5)
|
work := model.NewWorkRequest("test", script, "", os.TempDir(), []string{}, 5)
|
||||||
assert.NotNil(t, work, "")
|
assert.NotNil(t, work, "")
|
||||||
printWorkMessages(work)
|
printWorkMessages(work)
|
||||||
err := run(work)
|
err := run(work)
|
||||||
|
@ -60,7 +61,7 @@ func TestWorkRunnerWithError(t *testing.T) {
|
||||||
func TestWorkRunnerWithTimeout(t *testing.T) {
|
func TestWorkRunnerWithTimeout(t *testing.T) {
|
||||||
logger.Init("debug")
|
logger.Init("debug")
|
||||||
script := "../../tests/test_timeout.sh"
|
script := "../../tests/test_timeout.sh"
|
||||||
work := model.NewWorkRequest("test", script, "", []string{}, 1)
|
work := model.NewWorkRequest("test", script, "", os.TempDir(), []string{}, 1)
|
||||||
assert.NotNil(t, work, "")
|
assert.NotNil(t, work, "")
|
||||||
printWorkMessages(work)
|
printWorkMessages(work)
|
||||||
err := run(work)
|
err := run(work)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user