Compare commits

...
3 Commits
Author SHA1 Message Date
alvierahman90 8677f5bfdd Move config to separate package 2021-08-04 12:04:01 +01:00
alvierahman90 2189ee511c Allow executed script to have arguments 2021-08-04 11:51:40 +01:00
alvierahman90 a2d4153c64 Create Makefile 2021-08-04 10:22:19 +01:00
8 changed files with 128 additions and 78 deletions
+18
View File
@@ -0,0 +1,18 @@
all: install
clean:
rm -rf gohookr
install:
go mod tidy
go build -o gohookr
cp gohookr /usr/local/bin/
cp gohookr.service /usr/lib/systemd/system/
cp config.json /etc/gohookr.json
systemctl daemon-reload
systemctl enable --now gohookr
uninstall:
systemctl disable --now gohookr
rm -rf /usr/local/bin/gohookr /usr/lib/systemd/system/gohookr.service
systemctl daemon-reload
+6 -3
View File
@@ -2,13 +2,16 @@
"ListenAddress": "127.0.0.1:8654",
"Services": {
"test": {
"Script": "./example.sh",
"Script": {
"Program": "./example.sh",
"AppendPayload": true
},
"Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature",
"Tests": [
{
"Command": "git",
"Arguments": [ "pull" ]
"Program": "echo",
"Arguments": [ "test" ]
}
]
}
+19
View File
@@ -0,0 +1,19 @@
package config
import "os/exec"
type Command struct {
Program string
Arguments []string
AppendPayload bool
}
func (c Command) Execute(payload string) ([]byte, error) {
arguments := make([]string, 0)
copy(c.Arguments, arguments)
if c.AppendPayload {
arguments = append(arguments, payload)
}
return exec.Command(c.Program, arguments...).Output()
}
+39
View File
@@ -0,0 +1,39 @@
package config
import (
"encoding/json"
"fmt"
)
type Config struct {
ListenAddress string
Services map[string]struct {
Script Command
Secret string
SignatureHeader string
Tests []Command
}
}
func (c Config) Validate() error {
if c.ListenAddress == "" {
return requiredFieldError{"ListenAddress", ""}
}
jsonbytes, _ := json.MarshalIndent(c, "", " ")
fmt.Println(string(jsonbytes))
for serviceName, service := range c.Services {
if service.Script.Program == "" {
return requiredFieldError{"Script.Program", serviceName}
}
if service.SignatureHeader == "" {
return requiredFieldError{"SignatureHeader", serviceName}
}
if service.Secret == "" {
return requiredFieldError{"Secret", serviceName}
}
}
return nil
}
+12
View File
@@ -0,0 +1,12 @@
package config
import "fmt"
type requiredFieldError struct {
fieldName string
serviceName string
}
func (e requiredFieldError) Error() string {
return fmt.Sprintf("%v cannot be empty (%v)", e.fieldName, e.serviceName)
}
+1
View File
@@ -1,2 +1,3 @@
#!/usr/bin/bash
date >> test_output
echo "$1" >> test_output
+17 -63
View File
@@ -11,29 +11,19 @@ import (
"log"
"net/http"
"os"
"os/exec"
"git.alra.uk/alvierahman90/gohookr/config"
"github.com/gorilla/mux"
)
var config_filename = "/etc/gohookr.json"
var checkSignature = true
var config Config
var c config.Config
func main() {
r := mux.NewRouter()
r.HandleFunc("/webhooks/{service}", webhookHandler)
raw_config, err := ioutil.ReadFile(config_filename)
if err != nil {
panic(err.Error())
}
config = Config{}
json.Unmarshal(raw_config, &config)
if err := config.Validate(); err != nil {
panic(err.Error())
}
if p, ok := os.LookupEnv("CONFIG"); ok {
config_filename = p
}
@@ -42,7 +32,17 @@ func main() {
checkSignature = p != "true"
}
log.Fatal(http.ListenAndServe(config.ListenAddress, r))
raw_config, err := ioutil.ReadFile(config_filename)
if err != nil {
panic(err.Error())
}
json.Unmarshal(raw_config, &c)
if err := c.Validate(); err != nil {
panic(err.Error())
}
log.Fatal(http.ListenAndServe(c.ListenAddress, r))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
@@ -55,7 +55,7 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
}
// check what service is specified in URL (/webhooks/{service}) and if it exists
service, ok := config.Services[string(mux.Vars(r)["service"])]
service, ok := c.Services[string(mux.Vars(r)["service"])]
if !ok {
writeResponse(w, 404, "Service Not Found")
return
@@ -73,15 +73,15 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Run tests, immediately stop if one fails
for _, test := range service.Tests {
if _, err := exec.Command(test.Command, test.Arguments...).Output(); err != nil {
if _, err := test.Execute(payload); err != nil {
writeResponse(w, 409,
fmt.Sprintf("409 Conflict: Test failed: %v", err.Error()),
fmt.Sprintf("Conflict: Test failed: %v", err.Error()),
)
return
}
}
if stdout, err := exec.Command(service.Script, payload).Output(); err != nil {
if stdout, err := service.Script.Execute(payload); err != nil {
writeResponse(w, 500, err.Error())
return
} else {
@@ -100,49 +100,3 @@ func getSha256HMACSignature(secret []byte, data string) string {
io.WriteString(h, data)
return hex.EncodeToString(h.Sum(nil))
}
func (c Config) Validate() error {
if c.ListenAddress == "" {
return requiredFieldError{"ListenAddress", ""}
}
for serviceName, service := range c.Services {
if service.Script == "" {
return requiredFieldError{"Script", serviceName}
}
if service.SignatureHeader == "" {
return requiredFieldError{"SignatureHeader", serviceName}
}
if service.Secret == "" {
return requiredFieldError{"Secret", serviceName}
}
}
return nil
}
type Test struct {
Command string
Arguments []string
}
type Service struct {
Script string
Secret string
SignatureHeader string
Tests []Test
}
type Config struct {
ListenAddress string
Services map[string]Service
}
type requiredFieldError struct {
fieldName string
serviceName string
}
func (e requiredFieldError) Error() string {
return fmt.Sprintf("%v cannot be empty (%v)", e.fieldName, e.serviceName)
}
+16 -12
View File
@@ -12,12 +12,7 @@ Check below for an example configuration.
After you [install go](https://golang.org/doc/install):
```
go mod tidy
go build
cp gohookr /usr/local/bin/
cp gohookr.service /usr/lib/systemd/system/
systemctl daemon-reload
systemctl enable --now gohookr
make
```
## Signature Verification
@@ -32,7 +27,13 @@ You should also specify a shared secret in the `Secret` key.
You can disable signature verification altogether by setting environment variable
`NO_SIGNATURE_VERIFICATION` to `true`.
## Tests
## Writing Commands
gohookr doesn't care what the command is as long as the `Program` is executable.
You can specify extra arguments with the `Arguments` field.
You can ask it to put the payload as the last argument by setting `AppendPayload` to true.
## Writing Tests
gohookr can run test before running your script.
Tests must be in the form of bash scripts.
@@ -40,13 +41,13 @@ A non-zero return code is considered a fail and gohookr will run no further test
deploy.
Tests are run in the order they're listed so any actions that need to be done before
real tests are run can simply be put before the tests.
tests are run can simply be put in this section before the tests.
## Example Config
Required config keys are `ListenAddress` and `Services`.
Requried keys per service are `Script`, `Secret`, `SignatureHeader`.
Requried keys per service are `Script.Program`, `Secret`, `SignatureHeader`.
An example config file can be found [here](./config.json) but also below:
@@ -55,13 +56,16 @@ An example config file can be found [here](./config.json) but also below:
"ListenAddress": "127.0.0.1:8654",
"Services": {
"test": {
"Script": "./example.sh",
"Script": {
"Program": "./example.sh",
"AppendPayload": true
},
"Secret": "THISISVERYSECRET",
"SignatureHeader": "X-Gitea-Signature",
"Tests": [
{
"Command": "git",
"Arguments": [ "pull" ]
"Program": "echo",
"Arguments": [ "test" ]
}
]
}