commit fb7afbc549b95fbaf7257331e6c9448902c7eb5c Author: Nicolas Carlier Date: Fri Sep 19 18:46:04 2014 +0000 Init. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a5d8f72 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +dist/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..974ad96 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Nicolas Carlier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..79cc932 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +.SILENT : +.PHONY : build clean fmt + +TAG:=`git describe --abbrev=0 --tags` +LDFLAGS:=-X main.buildVersion $(TAG) +APPNAME:=webhookd + +all: build + +build: + echo "Building $(APPNAME)..." + go build -ldflags "$(LDFLAGS)" -o bin/$(APPNAME) ./src + +clean: clean-dist + rm -rf bin + +clean-dist: + rm -rf dist + +dist: clean-dist + mkdir -p dist/linux/amd64 && GOOS=linux GOARCH=amd64 go build -o dist/linux/amd64/$(APPNAME) ./src +# mkdir -p dist/linux/i386 && GOOS=linux GOARCH=386 go build -o dist/linux/i386/$APPNAME ./src + +release: dist +# godep restore + tar -cvzf cloudconfd-linux-amd64-$(TAG).tar.gz -C dist/linux/amd64 $(APPNAME) +# tar -cvzf cloudconfd-linux-i386-i386$(TAG).tar.gz -C dist/linux/i386 $(APPNAME) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b816bef --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +webhookd +========= + +A very simple webhook server to launch shell script. + diff --git a/assets/bitbucket.json b/assets/bitbucket.json new file mode 100644 index 0000000..5422fe0 --- /dev/null +++ b/assets/bitbucket.json @@ -0,0 +1,37 @@ +{ + "canon_url": "https://bitbucket.org", + "commits": [ + { + "author": "marcus", + "branch": "master", + "files": [ + { + "file": "somefile.py", + "type": "modified" + } + ], + "message": "Added some more things to somefile.py\n", + "node": "620ade18607a", + "parents": [ + "702c70160afc" + ], + "raw_author": "Marcus Bertrand ", + "raw_node": "620ade18607ac42d872b568bb92acaa9a28620e9", + "revision": null, + "size": -1, + "timestamp": "2012-05-30 05:58:56", + "utctimestamp": "2012-05-30 03:58:56+00:00" + } + ], + "repository": { + "absolute_url": "/marcus/project-x/", + "fork": false, + "is_private": true, + "name": "Project X", + "owner": "marcus", + "scm": "git", + "slug": "project-x", + "website": "https://atlassian.com/" + }, + "user": "marcus" +} diff --git a/assets/github.json b/assets/github.json new file mode 100644 index 0000000..dfb1fc2 --- /dev/null +++ b/assets/github.json @@ -0,0 +1,140 @@ +{ + "ref": "refs/heads/gh-pages", + "after": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40", + "before": "993b46bdfc03ae59434816829162829e67c4d490", + "created": false, + "deleted": false, + "forced": false, + "compare": "https://github.com/baxterthehacker/public-repo/compare/993b46bdfc03...4d2ab4e76d0d", + "commits": [ + { + "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40", + "distinct": true, + "message": "Trigger pages build", + "timestamp": "2014-07-25T12:37:40-04:00", + "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40", + "author": { + "name": "Kyle Daigle", + "email": "kyle.daigle@github.com", + "username": "kdaigle" + }, + "committer": { + "name": "Kyle Daigle", + "email": "kyle.daigle@github.com", + "username": "kdaigle" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "index.html" + ] + } + ], + "head_commit": { + "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40", + "distinct": true, + "message": "Trigger pages build", + "timestamp": "2014-07-25T12:37:40-04:00", + "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40", + "author": { + "name": "Kyle Daigle", + "email": "kyle.daigle@github.com", + "username": "kdaigle" + }, + "committer": { + "name": "Kyle Daigle", + "email": "kyle.daigle@github.com", + "username": "kdaigle" + }, + "added": [ + + ], + "removed": [ + + ], + "modified": [ + "index.html" + ] + }, + "repository": { + "id": 20000106, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://github.com/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "created_at": 1400625583, + "updated_at": "2014-07-01T17:21:25Z", + "pushed_at": 1406306262, + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 612, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 25, + "forks": 0, + "open_issues": 25, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "baxterthehacker", + "email": "baxterthehacker@users.noreply.github.com" + } +} diff --git a/scripts/bitbucket/echo.sh b/scripts/bitbucket/echo.sh new file mode 100755 index 0000000..db1c12f --- /dev/null +++ b/scripts/bitbucket/echo.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "bitbucket echo: $@" + diff --git a/scripts/github/echo.sh b/scripts/github/echo.sh new file mode 100755 index 0000000..4274c42 --- /dev/null +++ b/scripts/github/echo.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "github echo: $@" + diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..015c236 --- /dev/null +++ b/src/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "encoding/json" + "errors" + "flag" + "fmt" + "github.com/gorilla/mux" + "io" + "log" + "net/http" + "os/exec" +) + +var ( + laddr = flag.String("l", ":8080", "HTTP service address (e.g.address, ':8080')") +) + +type Record interface { + GetGitURL() string + GetName() string +} + +type BitbucketRecord struct { + Repository struct { + Slug string `json:"slug"` + Name string `json:"name"` + URL string `json:"absolute_url"` + } `json:"repository"` + BaseURL string `json:"canon_url"` + User string `json:"user"` +} + +func (r BitbucketRecord) GetGitURL() string { + return fmt.Sprintf("%s%s", r.BaseURL, r.Repository.URL) +} + +func (r BitbucketRecord) GetName() string { + return r.Repository.Name +} + +type GithubRecord struct { + Repository struct { + Name string `json:"name"` + URL string `json:"git_url"` + } `json:"repository"` +} + +func (r GithubRecord) GetGitURL() string { + return r.Repository.URL +} + +func (r GithubRecord) GetName() string { + return r.Repository.Name +} + +func RecordFactory(hookname string) (Record, error) { + switch hookname { + case "bitbucket": + return new(BitbucketRecord), nil + case "github": + return new(GithubRecord), nil + default: + return nil, errors.New("Unknown hookname.") + } +} + +type flushWriter struct { + f http.Flusher + w io.Writer +} + +func (fw *flushWriter) Write(p []byte) (n int, err error) { + n, err = fw.w.Write(p) + if fw.f != nil { + fw.f.Flush() + } + return +} + +func RunScript(w http.ResponseWriter, hook string, action string, params ...string) { + fw := flushWriter{w: w} + if f, ok := w.(http.Flusher); ok { + fw.f = f + } + scriptname := fmt.Sprintf("./scripts/%s/%s.sh", hook, action) + log.Println("Exec script: ", scriptname) + cmd := exec.Command(scriptname, params...) + cmd.Stdout = &fw + cmd.Stderr = &fw + cmd.Run() +} + +func Handler(w http.ResponseWriter, r *http.Request) { + params := mux.Vars(r) + hookname := params["hookname"] + action := params["action"] + + log.Println("Hook name: ", hookname) + + var record, err = RecordFactory(hookname) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + decoder := json.NewDecoder(r.Body) + err = decoder.Decode(&record) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + RunScript(w, hookname, action, record.GetGitURL(), record.GetName()) +} + +func main() { + flag.Parse() + + rtr := mux.NewRouter() + rtr.HandleFunc("/{hookname:[a-z]+}/{action:[a-z]+}", Handler).Methods("POST") + + http.Handle("/", rtr) + + log.Println("webhookd server listening...") + log.Fatal(http.ListenAndServe(*laddr, nil)) +}