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 }