summaryrefslogtreecommitdiff
path: root/main.go
blob: e88d655c658af155bca79b86520af684d91f12cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"
)

type cynciWebhookPayload struct {
	RepoName string `json:"repo"`
}

type configJSON struct {
	WorkDir       string            `json:"workdir"`
	Handlers      map[string]string `json:"handlers"`
	WebhookTokens map[string]string `json:"webhook_tokens"` // map[token_name]token_value
}

var config configJSON

var listenAddr = flag.String("listen", ":8080", "the address for HTTP to listen on")
var dataPath = flag.String("data.dir", "./data", "the path to the data directory")

func main() {
	flag.Parse()

	loadConfig()

	http.HandleFunc("/webhook/src", handleWebhookSrc)

	http.ListenAndServe(*listenAddr, nil)
}

func loadConfig() {
	configPath := filepath.Join(*dataPath, "config.json")
	configFile, err := ioutil.ReadFile(configPath)
	if err != nil {
		log.Fatalf("failed to read config file: %v", err)
	}

	err = json.Unmarshal(configFile, &config)
	if err != nil {
		log.Fatalf("failed to parse config: %v", err)
	}
}

func executeHandler(repoName string) error {
	handler, ok := config.Handlers[repoName]
	if !ok {
		return errors.New("no handler found")
	}

	handler = filepath.Join(*dataPath, handler)

	args := []string{handler}

	dt := time.Now().UTC().Format("2006-01-02_15-04-05.999")
	logPath := filepath.Join(*dataPath, fmt.Sprintf("logs/%s_%s.log", repoName, dt))

	logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_RDWR, 0666)
	if err != nil {
		return errors.New("failed to create log file")
	}
	defer logFile.Close()

	// Run the actual handler
	cmd := exec.Command(handler, args...)
	cmd.Stdout = logFile
	cmd.Stderr = logFile
	cmd.Dir = *dataPath

	if config.WorkDir != "" {
		cmd.Dir = config.WorkDir
	}

	err = cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to execute handler: %v", err)
	}

	return nil
}

func handleWebhookSrc(rw http.ResponseWriter, req *http.Request) {
	if !validateTokenHeader(rw, req) {
		return
	}

	var payload cynciWebhookPayload
	err := json.NewDecoder(req.Body).Decode(&payload)
	if err != nil {
		log.Printf("failed to decode payload: %v", err)
		http.Error(rw, "error", 500)
		return
	}

	// Execute the handler for this repo
	err = executeHandler(payload.RepoName)
	if err != nil {
		log.Print(err)
		http.Error(rw, "error", 500)
		return
	}
}

func validateTokenHeader(rw http.ResponseWriter, req *http.Request) bool {
	tokenHeader := req.Header.Get("X-CynCI-Token")

	for tokenName, token := range config.WebhookTokens {
		if token == tokenHeader {
			log.Printf("successful auth from: %s", tokenName)
			return true
		}
	}

	http.Error(rw, "Unauthorized", http.StatusUnauthorized)

	return false
}