Repository navigation
Expand file tree
/
Copy pathwebhook.go
More file actions
133 lines (109 loc) · 3.13 KB
/
Copy pathwebhook.go
File metadata and controls
133 lines (109 loc) · 3.13 KB
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
128
129
130
131
132
133
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"path/filepath"
"strings"
"time"
"github.com/gofrs/flock"
)
type WebhookPayload struct {
Repository struct {
FullName string `json:"full_name"`
} `json:"repository"`
Ref string `json:"ref"`
Installation struct {
ID int64 `json:"id"`
} `json:"installation"`
}
func handleWebhook(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if r.Header.Get("X-GitHub-Event") != "push" {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Unsupported event type"))
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading request body: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
signature := r.Header.Get("X-Hub-Signature-256")
if !verifySignature(body, signature, config.GitHubApp.webhookSecret) {
log.Printf("Invalid webhook signature")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var payload WebhookPayload
if err := json.Unmarshal(body, &payload); err != nil {
log.Printf("Error parsing webhook payload: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
branch := strings.TrimPrefix(payload.Ref, "refs/heads/")
var repoConfig *RepoConfig
for _, repo := range config.Projects {
if repo.Repository == payload.Repository.FullName && repo.Branch == branch {
repoConfig = repo
break
}
}
if repoConfig == nil {
log.Printf("No configuration found for repo: %s, branch: %s", payload.Repository.FullName, branch)
w.WriteHeader(http.StatusOK)
w.Write([]byte("No configuration found"))
return
}
log.Printf("Processing repo %s#%s", repoConfig.Repository, repoConfig.Branch)
go processWebhook(payload, *repoConfig)
w.WriteHeader(http.StatusOK)
}
func verifySignature(payload []byte, signature, secret string) bool {
if signature == "" {
return false
}
signature = strings.TrimPrefix(signature, "sha256=")
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expectedMAC := mac.Sum(nil)
expectedSignature := hex.EncodeToString(expectedMAC)
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func processWebhook(payload WebhookPayload, repoConfig RepoConfig) {
token, err := getAccessToken(payload.Installation.ID)
if err != nil {
log.Printf("Error getting credentials: %v", err)
return
}
lockPath := filepath.Join(filepath.Dir(repoConfig.Path), "."+filepath.Base(repoConfig.Path)+".lock")
lock := flock.New(lockPath)
locked, err := lock.TryLock()
if err != nil {
log.Printf("Failed to acquire lock: %v", err)
return
}
if !locked {
log.Printf("Waiting for lock on %s", repoConfig.Path)
err := lock.Lock()
if err != nil {
log.Printf("Failed to acquire lock: %v", err)
return
}
}
defer lock.Unlock()
started := time.Now()
if err := deploy(token, repoConfig); err != nil {
log.Printf("Error processing repository: %v", err)
return
}
log.Printf("Webhook for %s#%s finished in %v", repoConfig.Repository, repoConfig.Branch, time.Since(started))
}