init: working hooks

This commit is contained in:
HeshamTB 2023-09-19 23:47:19 +03:00
commit 056a4a963c
7 changed files with 396 additions and 0 deletions

24
auth.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"log"
"net/http"
)
type AuthHandler struct {
Handler http.Handler
}
func (l *AuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
AuthHeader := r.Header.Get("Authorization")
if AuthHeader != task.Auth {
log.Printf("Dropping request from %s.", r.RemoteAddr)
return // drop
}
l.Handler.ServeHTTP(w, r)
}
func NewAuth(handler http.Handler) AuthHandler {
return AuthHandler{handler}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module gitea.hbanafa.com/hesham/hooker
go 1.21.1

101
hooker.go Normal file
View File

@ -0,0 +1,101 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"sync"
"time"
)
/*
Listen for git web hooks and then execute a script.
*/
type TaskDefErr struct {
message string
}
func (e *TaskDefErr) Error() string {
return e.message
}
type TaskManifest struct {
Owner string
RepoID uint64
Auth string
Command string // Path to executable
Lock *sync.Mutex
}
var httpServer http.Server
var task TaskManifest
func InitHTTPHandler(handler *http.ServeMux) *LoggingHTTPHandler {
auth := NewAuth(handler)
loggingHandler := NewLogger(&auth)
return &loggingHandler
}
// Try to get an env var, if not set, panic or fatal
func getEnv(name string) string {
varVal := os.Getenv(name)
if varVal == "" {
panic(fmt.Sprintf("required %s env variable is not set", name))
}
return varVal
}
func InitTask() TaskManifest {
RepoID := getEnv("REPO_ID")
repoID, err := strconv.ParseUint(RepoID, 10, 64)
if err != nil {
panic("REPO_ID is not a valid int")
}
return TaskManifest{
Command: getEnv("CMD"),
Owner: getEnv("OWNER"),
Auth: getEnv("AUTH"),
RepoID: repoID,
Lock: &sync.Mutex{},
}
}
func init() {
TAG := "[ init ]"
l := func(msg string) {
log.Println(TAG, msg)
}
l("starting")
l("Validating Task")
task = InitTask()
l("Registering handlers")
mux := http.NewServeMux()
mux.HandleFunc("/", handleRoot)
handler := InitHTTPHandler(mux)
httpServer = http.Server{
ReadTimeout: time.Second * 5,
WriteTimeout: time.Second * 5,
Addr: ":4184",
Handler: handler,
}
}
func main() {
log.Println("Task", task)
log.Printf("Listening on %s", httpServer.Addr)
log.Fatal(httpServer.ListenAndServe())
log.Println("Server Stopped")
}

94
job.go Normal file
View File

@ -0,0 +1,94 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
type Owner struct {
Login string `json:"login"`
Username string `json:"username"`
}
type Repo struct {
Name string `json:"name"`
ID uint64 `json:"id"`
Owner Owner `json:"owner"`
}
type JsonRoot struct {
Repo Repo `json:"repository"`
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
return // Drop
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Could not read request body", http.StatusBadRequest)
return
}
var JsonRoot JsonRoot
err = json.Unmarshal(body, &JsonRoot); if err != nil {
log.Println("Can not Unmarshal body json")
http.Error(w,"Can not Unmarshal body json", http.StatusBadRequest)
return
}
fmt.Printf("JsonRoot: %v\n", JsonRoot)
if JsonRoot.Repo.ID != task.RepoID {
log.Printf(
"Repo ID sent: %d, Expecting: %d.",
JsonRoot.Repo.ID,
task.RepoID,
)
w.WriteHeader(http.StatusNotFound)
return
}
if JsonRoot.Repo.Owner.Login != task.Owner {
log.Printf(
"Owner sent: %s, Expecting: %s",
JsonRoot.Repo.Owner.Login,
task.Owner,
)
w.WriteHeader(http.StatusNotFound)
return
}
DeliveryUUID := findXDeliveryUUID(r)
if DeliveryUUID == "" {
log.Println("Could not find Delivery UUID")
w.WriteHeader(http.StatusBadRequest)
return
}
log.Println("Starting New Job", DeliveryUUID)
go RunJob(&task, DeliveryUUID, log.Default())
}
func findXDeliveryUUID(r *http.Request) string {
var headerVal string
possibleHeaders := []string{
"X-Gitea-Delivery",
"X-GitHub-Delivery",
"X-Gogs-Delivery",
}
for _, head := range possibleHeaders {
headerVal = r.Header.Get(head)
if headerVal != "" { break }
}
return headerVal
}

85
job_dep.go Normal file
View File

@ -0,0 +1,85 @@
package main
/*
This is scrapped for later use.
Now make a simple, one task runner with params
defined as env vars.
*/
import (
"os/exec"
"time"
)
type JobStatus uint64
const (
NEW = iota
RUNNING
FINISHED
FAILED
)
type Job interface {
Start()
Result() string
}
// Tasks are defined via config and are used to run jobs
type Task struct {
RepoFullName string
Owner string
Secret string
command exec.Cmd
}
type ShellCommandJob struct {
JobStatus
Task
UUID string
ExitCode uint8
stdout string
err *error
TimeCreated time.Time
TimeStarted time.Time
TimeFinished time.Time // This is redundant but keep it for now
TimeConsumed time.Duration
}
// TODO: Replace with task method to generate jobs
func NewShellJob(t Task, uuid string) *ShellCommandJob {
timeNow := time.Now().UTC()
job := ShellCommandJob{
JobStatus: NEW,
Task: t,
UUID: uuid,
TimeCreated: timeNow,
}
return &job
}
func (j *ShellCommandJob) Start() {
j.TimeStarted = time.Now().UTC()
stdout, err := j.command.Output()
if err != nil {
j.JobStatus = FAILED
}
j.TimeFinished = time.Now().UTC()
j.TimeConsumed = time.Since(j.TimeStarted)
j.stdout = string(stdout)
}
func (j *ShellCommandJob) Result() string {
return j.stdout
}
func (j *ShellCommandJob) String() string {
switch j.JobStatus {
case 0: return "NEW"
case 1: return "RUNNING"
case 2: return "FINISHED"
case 3: return "FAILD"
default: return "UNDEFINED"
}
}

28
logging.go Normal file
View File

@ -0,0 +1,28 @@
package main
import (
"fmt"
"log"
"net/http"
"time"
)
type LoggingHTTPHandler struct {
Handler http.Handler
}
func (l *LoggingHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t1 := time.Now().UTC()
l.Handler.ServeHTTP(w, r)
log.Println(
fmt.Sprintf("%s %s %s %v", r.RemoteAddr, r.Method, r.URL.Path, time.Since(t1)),
)
}
func NewLogger(handler http.Handler) LoggingHTTPHandler {
return LoggingHTTPHandler{handler}
}

61
runner.go Normal file
View File

@ -0,0 +1,61 @@
package main
import (
"bytes"
"io"
"log"
"os/exec"
"strings"
)
// Starts the task report it's exit status
// To Logger
func RunJob(task *TaskManifest, deliveryUUID string, logger *log.Logger) {
var stderrBuf, stdoutBuf bytes.Buffer
execuatable, args := CommandStrtoProgArgs(task.Command)
cmd := exec.Command(execuatable, args...)
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
task.Lock.Lock()
err := cmd.Run()
task.Lock.Unlock()
if err != nil {
logger.Println("Exec reported error", err.Error())
return
}
stdout, err := io.ReadAll(&stdoutBuf)
if err != nil {
logger.Printf("Could not read stdout from command for job %s\n", deliveryUUID)
return
}
stderr, err := io.ReadAll(&stderrBuf)
if err != nil {
logger.Printf("Could not read stderr from command for job %s\n", deliveryUUID)
return
}
logger.Printf("stdout: %v", string(stdout))
logger.Printf("stderr: %v", string(stderr))
logger.Printf("Job: %s exited with code: %d", deliveryUUID, cmd.ProcessState.ExitCode())
}
// Seperate executable from args in command string
func CommandStrtoProgArgs(cmd string) (Execuatbale string, Args []string) {
tokens := strings.Split(cmd, " ")
if len(tokens) == 1 {
return tokens[0], nil
}
return tokens[0], tokens[1:]
}