-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
145 lines (121 loc) · 4.1 KB
/
main.go
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
134
135
136
137
138
139
140
141
142
143
144
145
package main
import (
"fmt"
"net/http"
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-playground/webhooks/v6/github"
log "github.com/sirupsen/logrus"
)
func main() {
var (
gitBranch = os.Getenv("GIT_BRANCH")
githubSecret = os.Getenv("GITHUB_SECRET")
gitProvider = os.Getenv("GIT_PROVIDER")
repositoryURL = os.Getenv("REPOSITORY_URL")
sshPrivateKey = []byte(os.Getenv("SSH_PRIVATE_KEY"))
workingDir = os.Getenv("WORKING_DIR")
)
if err := checkoutGitRepository(sshPrivateKey, workingDir, repositoryURL, gitBranch); err != nil {
log.Fatalf("failed to checkout branch %q of repository %q into directory %q: %w", gitBranch, repositoryURL, workingDir, err)
}
var webhookHandler http.HandlerFunc
var err error
switch gitProvider {
case "github":
webhookHandler, err = handleGithubWebhook(gitBranch, githubSecret, workingDir, sshPrivateKey)
if err != nil {
log.Fatalf("failed to prepare webhook: %w", err)
}
default:
log.Fatalf("unknown git provider %q; current implementation only accepts \"github\"", gitProvider)
}
http.HandleFunc("/webhook", webhookHandler)
http.HandleFunc("/healthz", healthcheck)
log.Println("Listening for requests on port 80...")
if err := http.ListenAndServe(":80", nil); err != nil {
log.Fatalf("failed to start HTTP server: %w", err)
}
}
func checkoutGitRepository(sshPrivateKey []byte, workingDir, repositoryURL, gitBranch string) error {
publicKeys, err := ssh.NewPublicKeys("git", sshPrivateKey, "")
if err != nil {
return fmt.Errorf("failed to generate public keys: %w", err)
}
cloneOptions := git.CloneOptions{
Auth: publicKeys,
URL: repositoryURL,
}
repo, err := git.PlainClone(workingDir, false, &cloneOptions)
if err != nil {
return fmt.Errorf("failed to clone repository: %w", err)
}
worktree, err := repo.Worktree()
if err != nil {
return fmt.Errorf("cloned repository is broken: %w", err)
}
checkoutOptions := git.CheckoutOptions{
Branch: plumbing.ReferenceName(gitBranch),
}
if err := worktree.Checkout(&checkoutOptions); err != nil {
return fmt.Errorf("failed to checkout branch %q: %w", gitBranch, err)
}
return nil
}
func handleGithubWebhook(gitBranch, githubSecret, workingDir string, sshPrivateKey []byte) (http.HandlerFunc, error) {
githubWebhook, err := github.New(github.Options.Secret(githubSecret))
if err != nil {
return nil, fmt.Errorf("failed to set up GitHub webhook: %w", err)
}
handler := func(w http.ResponseWriter, r *http.Request) {
payload, err := githubWebhook.Parse(r, github.PushEvent)
switch err {
case nil: // no error, do nothing.
case github.ErrEventNotFound:
log.Info("webhook called for non-existing event, skipping")
default:
log.Errorf("could not parse payload: %q", err)
http.Error(w, "Invalid payload", http.StatusBadRequest)
return
}
pushEvent := payload.(github.PushPayload)
if pushEvent.Ref != gitBranch {
log.Info("webhook called for ref %q, skipping", pushEvent.Ref)
return
}
if err := updateRepository(workingDir, sshPrivateKey); err != nil {
log.Errorf("failed to update repository: %w", err)
http.Error(w, "Internal error", http.StatusInternalServerError)
return
}
log.Info("repository updated following event from GitHub")
}
return handler, nil
}
func updateRepository(workingDir string, sshPrivateKey []byte) error {
publicKeys, err := ssh.NewPublicKeys("git", sshPrivateKey, "")
if err != nil {
return fmt.Errorf("failed to generate public keys: %w", err)
}
repo, err := git.PlainOpen(workingDir)
if err != nil {
return fmt.Errorf("repository in %q directory is broken: %w", workingDir, err)
}
worktree, err := repo.Worktree()
if err != nil {
return fmt.Errorf("repository in %q directory is broken: %w", workingDir, err)
}
pullOptions := git.PullOptions{
Auth: publicKeys,
RemoteName: "origin",
}
if err := worktree.Pull(&pullOptions); err != nil {
return fmt.Errorf("failed to pull repository: %q", err)
}
return nil
}
func healthcheck(w http.ResponseWriter, r *http.Request) {
// The HTTP server is always healthy.
}