Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
caddy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
caddy
Commits
c6e2b9cc
Commit
c6e2b9cc
authored
Jun 03, 2015
by
Matt Holt
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #99 from xenolf/github-deploy
git: Webhook middleware for GitHub events
parents
4852f058
b4780a41
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
303 additions
and
6 deletions
+303
-6
config/setup/git.go
config/setup/git.go
+35
-6
middleware/git/git.go
middleware/git/git.go
+3
-0
middleware/git/webhook/github_hook.go
middleware/git/webhook/github_hook.go
+159
-0
middleware/git/webhook/github_hook_test.go
middleware/git/webhook/github_hook_test.go
+63
-0
middleware/git/webhook/webhook.go
middleware/git/webhook/webhook.go
+43
-0
No files found.
config/setup/git.go
View file @
c6e2b9cc
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/git"
"github.com/mholt/caddy/middleware/git"
"github.com/mholt/caddy/middleware/git/webhook"
)
)
// Git configures a new Git service routine.
// Git configures a new Git service routine.
...
@@ -20,13 +21,30 @@ func Git(c *Controller) (middleware.Middleware, error) {
...
@@ -20,13 +21,30 @@ func Git(c *Controller) (middleware.Middleware, error) {
return
nil
,
err
return
nil
,
err
}
}
// If a HookUrl is set, we switch to event based pulling.
// Install the url handler
if
repo
.
HookUrl
!=
""
{
c
.
Startup
=
append
(
c
.
Startup
,
func
()
error
{
return
repo
.
Pull
()
})
webhook
:=
&
webhook
.
WebHook
{
Repo
:
repo
}
return
func
(
next
middleware
.
Handler
)
middleware
.
Handler
{
webhook
.
Next
=
next
return
webhook
},
nil
}
else
{
c
.
Startup
=
append
(
c
.
Startup
,
func
()
error
{
c
.
Startup
=
append
(
c
.
Startup
,
func
()
error
{
// Start service routine in background
// Start service routine in background
git
.
Start
(
repo
)
git
.
Start
(
repo
)
// Do a pull right away to return error
// Do a pull right away to return error
return
repo
.
Pull
()
return
repo
.
Pull
()
})
})
}
return
nil
,
err
return
nil
,
err
}
}
...
@@ -75,6 +93,17 @@ func gitParse(c *Controller) (*git.Repo, error) {
...
@@ -75,6 +93,17 @@ func gitParse(c *Controller) (*git.Repo, error) {
if
t
>
0
{
if
t
>
0
{
repo
.
Interval
=
time
.
Duration
(
t
)
*
time
.
Second
repo
.
Interval
=
time
.
Duration
(
t
)
*
time
.
Second
}
}
case
"hook"
:
if
!
c
.
NextArg
()
{
return
nil
,
c
.
ArgErr
()
}
repo
.
HookUrl
=
c
.
Val
()
// optional secret for validation
if
c
.
NextArg
()
{
repo
.
HookSecret
=
c
.
Val
()
}
case
"then"
:
case
"then"
:
thenArgs
:=
c
.
RemainingArgs
()
thenArgs
:=
c
.
RemainingArgs
()
if
len
(
thenArgs
)
==
0
{
if
len
(
thenArgs
)
==
0
{
...
...
middleware/git/git.go
View file @
c6e2b9cc
...
@@ -59,6 +59,9 @@ type Repo struct {
...
@@ -59,6 +59,9 @@ type Repo struct {
lastPull
time
.
Time
// time of the last successful pull
lastPull
time
.
Time
// time of the last successful pull
lastCommit
string
// hash for the most recent commit
lastCommit
string
// hash for the most recent commit
sync
.
Mutex
sync
.
Mutex
HookUrl
string
// url to listen on for webhooks
HookSecret
string
// secret to validate hooks
}
}
// Pull attempts a git clone.
// Pull attempts a git clone.
...
...
middleware/git/webhook/github_hook.go
0 → 100644
View file @
c6e2b9cc
package
webhook
import
(
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"github.com/mholt/caddy/middleware/git"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
type
GithubHook
struct
{}
type
ghRelease
struct
{
Action
string
`json:"action"`
Release
struct
{
TagName
string
`json:"tag_name"`
Name
interface
{}
`json:"name"`
}
`json:"release"`
}
type
ghPush
struct
{
Ref
string
`json:"ref"`
}
// Logger is used to log errors; if nil, the default log.Logger is used.
var
Logger
*
log
.
Logger
// logger is an helper function to retrieve the available logger
func
logger
()
*
log
.
Logger
{
if
Logger
==
nil
{
Logger
=
log
.
New
(
os
.
Stderr
,
""
,
log
.
LstdFlags
)
}
return
Logger
}
func
(
g
GithubHook
)
DoesHandle
(
h
http
.
Header
)
bool
{
userAgent
:=
h
.
Get
(
"User-Agent"
)
// GitHub always uses a user-agent like "GitHub-Hookshot/<id>"
if
userAgent
!=
""
&&
strings
.
HasPrefix
(
userAgent
,
"GitHub-Hookshot"
)
{
return
true
}
return
false
}
func
(
g
GithubHook
)
Handle
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
repo
*
git
.
Repo
)
(
int
,
error
)
{
if
r
.
Method
!=
"POST"
{
return
http
.
StatusMethodNotAllowed
,
errors
.
New
(
"The request had an invalid method."
)
}
// read full body - required for signature
body
,
err
:=
ioutil
.
ReadAll
(
r
.
Body
)
err
=
g
.
handleSignature
(
r
,
body
,
repo
.
HookSecret
)
if
err
!=
nil
{
return
http
.
StatusBadRequest
,
err
}
event
:=
r
.
Header
.
Get
(
"X-Github-Event"
)
if
event
==
""
{
return
http
.
StatusBadRequest
,
errors
.
New
(
"The 'X-Github-Event' header is required but was missing."
)
}
switch
event
{
case
"ping"
:
w
.
Write
([]
byte
(
"pong"
))
case
"push"
:
err
:=
g
.
handlePush
(
body
,
repo
)
if
err
!=
nil
{
return
http
.
StatusBadRequest
,
err
}
case
"release"
:
err
:=
g
.
handleRelease
(
body
,
repo
)
if
err
!=
nil
{
return
http
.
StatusBadRequest
,
err
}
// return 400 if we do not handle the event type.
// This is to visually show the user a configuration error in the GH ui.
default
:
return
http
.
StatusBadRequest
,
nil
}
return
http
.
StatusOK
,
nil
}
// Check for an optional signature in the request
// if it is signed, verify the signature.
func
(
g
GithubHook
)
handleSignature
(
r
*
http
.
Request
,
body
[]
byte
,
secret
string
)
error
{
signature
:=
r
.
Header
.
Get
(
"X-Hub-Signature"
)
if
signature
!=
""
{
if
secret
==
""
{
logger
()
.
Print
(
"Unable to verify request signature. Secret not set in caddyfile!"
)
}
else
{
mac
:=
hmac
.
New
(
sha1
.
New
,
[]
byte
(
secret
))
mac
.
Write
(
body
)
expectedMac
:=
hex
.
EncodeToString
(
mac
.
Sum
(
nil
))
if
signature
[
5
:
]
!=
expectedMac
{
return
errors
.
New
(
"Could not verify request signature. The signature is invalid!"
)
}
}
}
return
nil
}
func
(
g
GithubHook
)
handlePush
(
body
[]
byte
,
repo
*
git
.
Repo
)
error
{
var
push
ghPush
err
:=
json
.
Unmarshal
(
body
,
&
push
)
if
err
!=
nil
{
return
err
}
// extract the branch being pushed from the ref string
// and if it matches with our locally tracked one, pull.
refSlice
:=
strings
.
Split
(
push
.
Ref
,
"/"
)
if
len
(
refSlice
)
!=
3
{
return
errors
.
New
(
"The push request contained an invalid reference string."
)
}
branch
:=
refSlice
[
2
]
if
branch
==
repo
.
Branch
{
logger
()
.
Print
(
"Received pull notification for the tracking branch, updating..."
)
repo
.
Pull
()
}
return
nil
}
func
(
g
GithubHook
)
handleRelease
(
body
[]
byte
,
repo
*
git
.
Repo
)
error
{
var
release
ghRelease
err
:=
json
.
Unmarshal
(
body
,
&
release
)
if
err
!=
nil
{
return
err
}
if
release
.
Release
.
TagName
==
""
{
return
errors
.
New
(
"The release request contained an invalid TagName."
)
}
logger
()
.
Printf
(
"Received new release '%s'. -> Updating local repository to this release."
,
release
.
Release
.
Name
)
// Update the local branch to the release tag name
// this will pull the release tag.
repo
.
Branch
=
release
.
Release
.
TagName
repo
.
Pull
()
return
nil
}
middleware/git/webhook/github_hook_test.go
0 → 100644
View file @
c6e2b9cc
package
webhook
import
(
"bytes"
"github.com/mholt/caddy/middleware/git"
"net/http"
"net/http/httptest"
"testing"
)
func
TestGithubDeployPush
(
t
*
testing
.
T
)
{
repo
:=
&
git
.
Repo
{
Branch
:
"master"
,
HookUrl
:
"/github_deploy"
,
HookSecret
:
"supersecret"
}
ghHook
:=
GithubHook
{}
for
i
,
test
:=
range
[]
struct
{
body
string
event
string
responseBody
string
code
int
}{
{
""
,
""
,
""
,
400
},
{
""
,
"push"
,
""
,
400
},
{
pushBodyOther
,
"push"
,
""
,
200
},
{
pushBodyPartial
,
"push"
,
""
,
400
},
{
""
,
"release"
,
""
,
400
},
{
""
,
"ping"
,
"pong"
,
200
},
}
{
req
,
err
:=
http
.
NewRequest
(
"POST"
,
"/github_deploy"
,
bytes
.
NewBuffer
([]
byte
(
test
.
body
)))
if
err
!=
nil
{
t
.
Fatalf
(
"Test %v: Could not create HTTP request: %v"
,
i
,
err
)
}
if
test
.
event
!=
""
{
req
.
Header
.
Add
(
"X-Github-Event"
,
test
.
event
)
}
rec
:=
httptest
.
NewRecorder
()
code
,
err
:=
ghHook
.
Handle
(
rec
,
req
,
repo
)
if
code
!=
test
.
code
{
t
.
Errorf
(
"Test %d: Expected response code to be %d but was %d"
,
i
,
test
.
code
,
code
)
}
if
rec
.
Body
.
String
()
!=
test
.
responseBody
{
t
.
Errorf
(
"Test %d: Expected response body to be '%v' but was '%v'"
,
i
,
test
.
responseBody
,
rec
.
Body
.
String
())
}
}
}
var
pushBodyPartial
=
`
{
"ref": ""
}
`
var
pushBodyOther
=
`
{
"ref": "refs/heads/some-other-branch"
}
`
middleware/git/webhook/webhook.go
0 → 100644
View file @
c6e2b9cc
package
webhook
import
(
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/git"
"net/http"
)
// Middleware for handling web hooks of git providers
type
WebHook
struct
{
Repo
*
git
.
Repo
Next
middleware
.
Handler
}
// Interface for specific providers to implement.
type
hookHandler
interface
{
DoesHandle
(
http
.
Header
)
bool
Handle
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
repo
*
git
.
Repo
)
(
int
,
error
)
}
// Slice of all registered hookHandlers.
// Register new hook handlers here!
var
handlers
=
[]
hookHandler
{
GithubHook
{},
}
// ServeHTTP implements the middlware.Handler interface.
func
(
h
WebHook
)
ServeHTTP
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
(
int
,
error
)
{
if
r
.
URL
.
Path
==
h
.
Repo
.
HookUrl
{
for
_
,
handler
:=
range
handlers
{
// if a handler indicates it does handle the request,
// we do not try other handlers. Only one handler ever
// handles a specific request.
if
handler
.
DoesHandle
(
r
.
Header
)
{
return
handler
.
Handle
(
w
,
r
,
h
.
Repo
)
}
}
}
return
h
.
Next
.
ServeHTTP
(
w
,
r
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment