mirror of
https://github.com/bytedream/scripts.git
synced 2025-05-09 12:15:12 +02:00
Added github webhook
This commit is contained in:
parent
08ee6e6fe4
commit
dadf26ff90
19
github-webhook/README.md
Normal file
19
github-webhook/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
## Github Webhook
|
||||
|
||||
A simple webhook server, which saves a github repository whenever changes were made.
|
||||
|
||||
Inspired from [this webhook api](https://github.com/go-playground/webhooks).
|
||||
The library is pretty useful, but too overloaded for my purposes.
|
||||
|
||||
#### Requires
|
||||
|
||||
- [gorilla/mux](https://github.com/gorilla/mux), but if you want to, you can easily cut out the gurilla/mux api and replace it with the api you prefer
|
||||
|
||||
### Usage
|
||||
|
||||
1. Create a repository [webhook](https://docs.github.com/en/developers/webhooks-and-events/creating-webhooks) on github.
|
||||
2. Not necessary, but highly recommended: Choose a [secret](https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks) for the webhook.
|
||||
3. Set the value of `gitTree` in [main.go](main.go#L19) to the directory you want to clone the repositories in.
|
||||
4. Set the value of `secret` in [main.go](main.go#L20) to the secret, you have chosen in step 2.
|
||||
5. Set the callback path in the `main` method in [main.go](main.go#L406) to the path you have chosen when creating the webhook.
|
||||
6. Set the port in the `main` method in [main.go](main.go#L408) to the port github is sending the webhook callback.
|
460
github-webhook/main.go
Normal file
460
github-webhook/main.go
Normal file
@ -0,0 +1,460 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// the directory to clone the repos in
|
||||
gitTree = ""
|
||||
secret = "" // please do not hardcode this, use .env files or something similar
|
||||
)
|
||||
|
||||
type payload interface{}
|
||||
|
||||
type PushPayload struct {
|
||||
payload
|
||||
|
||||
Ref string `json:"ref"`
|
||||
Before string `json:"before"`
|
||||
After string `json:"after"`
|
||||
Created bool `json:"created"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Forced bool `json:"forced"`
|
||||
BaseRef *string `json:"base_ref"`
|
||||
Compare string `json:"compare"`
|
||||
Commits []struct {
|
||||
Sha string `json:"sha"`
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
TreeID string `json:"tree_id"`
|
||||
Distinct bool `json:"distinct"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
URL string `json:"url"`
|
||||
Author struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
} `json:"author"`
|
||||
Committer struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
} `json:"committer"`
|
||||
Added []string `json:"added"`
|
||||
Removed []string `json:"removed"`
|
||||
Modified []string `json:"modified"`
|
||||
} `json:"commits"`
|
||||
HeadCommit struct {
|
||||
ID string `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
TreeID string `json:"tree_id"`
|
||||
Distinct bool `json:"distinct"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
URL string `json:"url"`
|
||||
Author struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
} `json:"author"`
|
||||
Committer struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
} `json:"committer"`
|
||||
Added []string `json:"added"`
|
||||
Removed []string `json:"removed"`
|
||||
Modified []string `json:"modified"`
|
||||
} `json:"head_commit"`
|
||||
Repository struct {
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Owner struct {
|
||||
Login string `json:"login"`
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
GravatarID string `json:"gravatar_id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
FollowersURL string `json:"followers_url"`
|
||||
FollowingURL string `json:"following_url"`
|
||||
GistsURL string `json:"gists_url"`
|
||||
StarredURL string `json:"starred_url"`
|
||||
SubscriptionsURL string `json:"subscriptions_url"`
|
||||
OrganizationsURL string `json:"organizations_url"`
|
||||
ReposURL string `json:"repos_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
ReceivedEventsURL string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
} `json:"owner"`
|
||||
Private bool `json:"private"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Description string `json:"description"`
|
||||
Fork bool `json:"fork"`
|
||||
URL string `json:"url"`
|
||||
ForksURL string `json:"forks_url"`
|
||||
KeysURL string `json:"keys_url"`
|
||||
CollaboratorsURL string `json:"collaborators_url"`
|
||||
TeamsURL string `json:"teams_url"`
|
||||
HooksURL string `json:"hooks_url"`
|
||||
IssueEventsURL string `json:"issue_events_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
AssigneesURL string `json:"assignees_url"`
|
||||
BranchesURL string `json:"branches_url"`
|
||||
TagsURL string `json:"tags_url"`
|
||||
BlobsURL string `json:"blobs_url"`
|
||||
GitTagsURL string `json:"git_tags_url"`
|
||||
GitRefsURL string `json:"git_refs_url"`
|
||||
TreesURL string `json:"trees_url"`
|
||||
StatusesURL string `json:"statuses_url"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
StargazersURL string `json:"stargazers_url"`
|
||||
ContributorsURL string `json:"contributors_url"`
|
||||
SubscribersURL string `json:"subscribers_url"`
|
||||
SubscriptionURL string `json:"subscription_url"`
|
||||
CommitsURL string `json:"commits_url"`
|
||||
GitCommitsURL string `json:"git_commits_url"`
|
||||
CommentsURL string `json:"comments_url"`
|
||||
IssueCommentURL string `json:"issue_comment_url"`
|
||||
ContentsURL string `json:"contents_url"`
|
||||
CompareURL string `json:"compare_url"`
|
||||
MergesURL string `json:"merges_url"`
|
||||
ArchiveURL string `json:"archive_url"`
|
||||
DownloadsURL string `json:"downloads_url"`
|
||||
IssuesURL string `json:"issues_url"`
|
||||
PullsURL string `json:"pulls_url"`
|
||||
MilestonesURL string `json:"milestones_url"`
|
||||
NotificationsURL string `json:"notifications_url"`
|
||||
LabelsURL string `json:"labels_url"`
|
||||
ReleasesURL string `json:"releases_url"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PushedAt int64 `json:"pushed_at"`
|
||||
GitURL string `json:"git_url"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
SvnURL string `json:"svn_url"`
|
||||
Homepage *string `json:"homepage"`
|
||||
Size int64 `json:"size"`
|
||||
StargazersCount int64 `json:"stargazers_count"`
|
||||
WatchersCount int64 `json:"watchers_count"`
|
||||
Language *string `json:"language"`
|
||||
HasIssues bool `json:"has_issues"`
|
||||
HasDownloads bool `json:"has_downloads"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
HasPages bool `json:"has_pages"`
|
||||
ForksCount int64 `json:"forks_count"`
|
||||
MirrorURL *string `json:"mirror_url"`
|
||||
OpenIssuesCount int64 `json:"open_issues_count"`
|
||||
Forks int64 `json:"forks"`
|
||||
OpenIssues int64 `json:"open_issues"`
|
||||
Watchers int64 `json:"watchers"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
Stargazers int64 `json:"stargazers"`
|
||||
MasterBranch string `json:"master_branch"`
|
||||
} `json:"repository"`
|
||||
Pusher struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
} `json:"pusher"`
|
||||
Sender struct {
|
||||
Login string `json:"login"`
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
GravatarID string `json:"gravatar_id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
FollowersURL string `json:"followers_url"`
|
||||
FollowingURL string `json:"following_url"`
|
||||
GistsURL string `json:"gists_url"`
|
||||
StarredURL string `json:"starred_url"`
|
||||
SubscriptionsURL string `json:"subscriptions_url"`
|
||||
OrganizationsURL string `json:"organizations_url"`
|
||||
ReposURL string `json:"repos_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
ReceivedEventsURL string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
} `json:"sender"`
|
||||
Installation struct {
|
||||
ID int `json:"id"`
|
||||
} `json:"installation"`
|
||||
}
|
||||
|
||||
type RepositoryPayload struct {
|
||||
Action string `json:"action"`
|
||||
Repository struct {
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Owner struct {
|
||||
Login string `json:"login"`
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
GravatarID string `json:"gravatar_id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
FollowersURL string `json:"followers_url"`
|
||||
FollowingURL string `json:"following_url"`
|
||||
GistsURL string `json:"gists_url"`
|
||||
StarredURL string `json:"starred_url"`
|
||||
SubscriptionsURL string `json:"subscriptions_url"`
|
||||
OrganizationsURL string `json:"organizations_url"`
|
||||
ReposURL string `json:"repos_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
ReceivedEventsURL string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
} `json:"owner"`
|
||||
Private bool `json:"private"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
Description string `json:"description"`
|
||||
Fork bool `json:"fork"`
|
||||
URL string `json:"url"`
|
||||
ForksURL string `json:"forks_url"`
|
||||
KeysURL string `json:"keys_url"`
|
||||
CollaboratorsURL string `json:"collaborators_url"`
|
||||
TeamsURL string `json:"teams_url"`
|
||||
HooksURL string `json:"hooks_url"`
|
||||
IssueEventsURL string `json:"issue_events_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
AssigneesURL string `json:"assignees_url"`
|
||||
BranchesURL string `json:"branches_url"`
|
||||
TagsURL string `json:"tags_url"`
|
||||
BlobsURL string `json:"blobs_url"`
|
||||
GitTagsURL string `json:"git_tags_url"`
|
||||
GitRefsURL string `json:"git_refs_url"`
|
||||
TreesURL string `json:"trees_url"`
|
||||
StatusesURL string `json:"statuses_url"`
|
||||
LanguagesURL string `json:"languages_url"`
|
||||
StargazersURL string `json:"stargazers_url"`
|
||||
ContributorsURL string `json:"contributors_url"`
|
||||
SubscribersURL string `json:"subscribers_url"`
|
||||
SubscriptionURL string `json:"subscription_url"`
|
||||
CommitsURL string `json:"commits_url"`
|
||||
GitCommitsURL string `json:"git_commits_url"`
|
||||
CommentsURL string `json:"comments_url"`
|
||||
IssueCommentURL string `json:"issue_comment_url"`
|
||||
ContentsURL string `json:"contents_url"`
|
||||
CompareURL string `json:"compare_url"`
|
||||
MergesURL string `json:"merges_url"`
|
||||
ArchiveURL string `json:"archive_url"`
|
||||
DownloadsURL string `json:"downloads_url"`
|
||||
IssuesURL string `json:"issues_url"`
|
||||
PullsURL string `json:"pulls_url"`
|
||||
MilestonesURL string `json:"milestones_url"`
|
||||
NotificationsURL string `json:"notifications_url"`
|
||||
LabelsURL string `json:"labels_url"`
|
||||
ReleasesURL string `json:"releases_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PushedAt time.Time `json:"pushed_at"`
|
||||
GitURL string `json:"git_url"`
|
||||
SSHURL string `json:"ssh_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
SvnURL string `json:"svn_url"`
|
||||
Homepage *string `json:"homepage"`
|
||||
Size int64 `json:"size"`
|
||||
StargazersCount int64 `json:"stargazers_count"`
|
||||
WatchersCount int64 `json:"watchers_count"`
|
||||
Language *string `json:"language"`
|
||||
HasIssues bool `json:"has_issues"`
|
||||
HasDownloads bool `json:"has_downloads"`
|
||||
HasWiki bool `json:"has_wiki"`
|
||||
HasPages bool `json:"has_pages"`
|
||||
ForksCount int64 `json:"forks_count"`
|
||||
MirrorURL *string `json:"mirror_url"`
|
||||
OpenIssuesCount int64 `json:"open_issues_count"`
|
||||
Forks int64 `json:"forks"`
|
||||
OpenIssues int64 `json:"open_issues"`
|
||||
Watchers int64 `json:"watchers"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
} `json:"repository"`
|
||||
Organization struct {
|
||||
Login string `json:"login"`
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
URL string `json:"url"`
|
||||
ReposURL string `json:"repos_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
MembersURL string `json:"members_url"`
|
||||
PublicMembersURL string `json:"public_members_url"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
} `json:"organization"`
|
||||
Sender struct {
|
||||
Login string `json:"login"`
|
||||
ID int64 `json:"id"`
|
||||
NodeID string `json:"node_id"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
GravatarID string `json:"gravatar_id"`
|
||||
URL string `json:"url"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
FollowersURL string `json:"followers_url"`
|
||||
FollowingURL string `json:"following_url"`
|
||||
GistsURL string `json:"gists_url"`
|
||||
StarredURL string `json:"starred_url"`
|
||||
SubscriptionsURL string `json:"subscriptions_url"`
|
||||
OrganizationsURL string `json:"organizations_url"`
|
||||
ReposURL string `json:"repos_url"`
|
||||
EventsURL string `json:"events_url"`
|
||||
ReceivedEventsURL string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
} `json:"sender"`
|
||||
}
|
||||
|
||||
type OnPayload func(payload payload, gw *GithubWebhook) error
|
||||
|
||||
type GithubWebhook struct {
|
||||
pushEvents map[int64][]OnPayload
|
||||
repositoryEvents map[int64][]OnPayload
|
||||
|
||||
repoNames map[int64]string
|
||||
}
|
||||
|
||||
func (gw *GithubWebhook) AddPushEvent(repoID int64, function OnPayload) {
|
||||
gw.pushEvents[repoID] = append(gw.pushEvents[repoID], function)
|
||||
}
|
||||
|
||||
func (gw *GithubWebhook) AddRepositoryEvent(repoID int64, function OnPayload) {
|
||||
gw.repositoryEvents[repoID] = append(gw.repositoryEvents[repoID], function)
|
||||
}
|
||||
|
||||
func (gw *GithubWebhook) OnWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
event := r.Header.Get("X-GitHub-Event")
|
||||
if event == "" {
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil || len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// when a secret is given, this checks if the webhook and the local secret are even
|
||||
if secret != "" {
|
||||
signature := r.Header.Get("X-Hub-Signature")
|
||||
if len(signature) == 0 {
|
||||
return
|
||||
}
|
||||
mac := hmac.New(sha1.New, []byte(secret))
|
||||
_, _ = mac.Write(payload)
|
||||
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
if !hmac.Equal([]byte(signature[5:]), []byte(expectedMAC)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch event {
|
||||
case "push":
|
||||
var pushPayload PushPayload
|
||||
if err = json.Unmarshal(payload, &pushPayload); err != nil {
|
||||
return
|
||||
}
|
||||
if events, ok := gw.pushEvents[pushPayload.Repository.ID]; ok {
|
||||
for _, event := range events {
|
||||
if err := event(pushPayload, gw); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "repository":
|
||||
var repositoryPayload RepositoryPayload
|
||||
if err = json.Unmarshal(payload, &repositoryPayload); err != nil {
|
||||
return
|
||||
}
|
||||
if events, ok := gw.repositoryEvents[repositoryPayload.Repository.ID]; ok {
|
||||
for _, event := range events {
|
||||
if err := event(repositoryPayload, gw); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := mux.NewRouter()
|
||||
gw := &GithubWebhook{pushEvents: make(map[int64][]OnPayload), repositoryEvents: make(map[int64][]OnPayload), repoNames: make(map[int64]string)}
|
||||
gw.AddPushEvent(364735060, updateLocal)
|
||||
gw.AddRepositoryEvent(364735060, renameLocal)
|
||||
|
||||
// NOTE: To find out the id of a repository, go to the repo page, open the browser / developer console and execute `document.head.querySelector("meta[name=octolytics-dimension-repository_id]").content`
|
||||
|
||||
r.HandleFunc("/github_webhook", gw.OnWebhook).Methods("POST")
|
||||
|
||||
http.ListenAndServe(":8080", r)
|
||||
}
|
||||
|
||||
func checkIfLocalPresent(gw *GithubWebhook, id int64, name string, cloneUrl string) error {
|
||||
if _, ok := gw.repoNames[id]; !ok {
|
||||
files, err := ioutil.ReadDir(gitTree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() && file.Name() == name {
|
||||
gw.repoNames[id] = name
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("git",
|
||||
"clone", cloneUrl,
|
||||
filepath.Join(gitTree, name))
|
||||
if err = cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
gw.repoNames[id] = name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameLocal(payload2 payload, gw *GithubWebhook) error {
|
||||
repoPayload := payload2.(RepositoryPayload)
|
||||
if repoPayload.Action == "rename" {
|
||||
if err := checkIfLocalPresent(gw, repoPayload.Repository.ID, repoPayload.Repository.Name, repoPayload.Repository.CloneURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.Rename(filepath.Join(gitTree, gw.repoNames[repoPayload.Repository.ID]), filepath.Join(gitTree, repoPayload.Repository.Name))
|
||||
gw.repoNames[repoPayload.Repository.ID] = repoPayload.Repository.Name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateLocal(payload2 payload, gw *GithubWebhook) error {
|
||||
pushPayload := payload2.(PushPayload)
|
||||
|
||||
if err := checkIfLocalPresent(gw, pushPayload.Repository.ID, pushPayload.Repository.Name, pushPayload.Repository.CloneURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return exec.Command("git",
|
||||
"-C", filepath.Join(gitTree, pushPayload.Repository.Name),
|
||||
"pull",
|
||||
"origin").Run()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user