refactor webhooks
This commit is contained in:
parent
3011e337ec
commit
f4cda06e64
58
README.md
58
README.md
|
@ -22,6 +22,31 @@ go build -mod=vendor .
|
|||
./git-deploy run --listen :3000 --serve-path ./overrides
|
||||
```
|
||||
|
||||
## Add Webhooks
|
||||
|
||||
To add a webhook you'll first need a secret
|
||||
|
||||
**with node.js**:
|
||||
|
||||
```js
|
||||
crypto.randomBytes(16).toString("hex");
|
||||
```
|
||||
|
||||
Then you'll need to set up the webhook in your platform of choice.
|
||||
|
||||
### Github
|
||||
|
||||
New Webhook: `https://github.com/YOUR_ORG/YOUR_REPO/settings/hooks/new`
|
||||
|
||||
```txt
|
||||
Payload URL: https://YOUR_DOMAIN/api/webhooks/github
|
||||
Content-Type: application/json
|
||||
Secret: YOUR_SECRET
|
||||
Which events would you like to trigger this webhook?
|
||||
Just the `push` event.
|
||||
Active: ✅
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
**git-deploy** is intended for use with static websites that are generated after
|
||||
|
@ -44,6 +69,39 @@ don't want to use it.
|
|||
don't want to use it. The built-in interface requires the built-in
|
||||
authentication.
|
||||
|
||||
## How to Generate a Base64 Secret
|
||||
|
||||
**in your browser**:
|
||||
|
||||
```js
|
||||
(async function () {
|
||||
var rnd = new Uint8Array(16);
|
||||
await crypto.getRandomValues(rnd);
|
||||
var b64 = [].slice
|
||||
.apply(rnd)
|
||||
.map(function (ch) {
|
||||
return String.fromCharCode(ch);
|
||||
})
|
||||
.join("");
|
||||
var secret = btoa(b64)
|
||||
.replace(/\//g, "_")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/=/g, "");
|
||||
console.info(secret);
|
||||
})();
|
||||
```
|
||||
|
||||
**with node.js**:
|
||||
|
||||
```js
|
||||
crypto
|
||||
.randomBytes(16)
|
||||
.toString("base64")
|
||||
.replace(/\+/g, "-")
|
||||
.replace(/\//g, "_")
|
||||
.replace(/=/g, "");
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2020. All rights reserved.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// // +build github
|
||||
// TODO omit github unless specified by build tag
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks/github"
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
package options
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
var Server *ServerConfig
|
||||
|
||||
type ServerConfig struct {
|
||||
Addr string
|
||||
TrustProxy bool
|
||||
Compress bool
|
||||
ServePath string
|
||||
}
|
||||
|
||||
var ServerFlags *flag.FlagSet
|
||||
var InitFlags *flag.FlagSet
|
||||
var DefaultMaxBodySize int64 = 1024 * 1024
|
||||
|
||||
func init() {
|
||||
Server = &ServerConfig{}
|
||||
ServerFlags = flag.NewFlagSet("run", flag.ExitOnError)
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
// // +build github
|
||||
|
||||
package main
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -9,17 +7,23 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"git.ryanburnette.com/ryanburnette/git-deploy/internal/options"
|
||||
"git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/google/go-github/v32/github"
|
||||
)
|
||||
|
||||
func init() {
|
||||
githubSecret := ""
|
||||
runFlags.StringVar(&githubSecret, "github-secret", "", "secret for github webhooks (same as GITHUB_SECRET=)")
|
||||
webhookProviders["github"] = registerGithubish("github", &githubSecret, "GITHUB_SECRET")
|
||||
var githubSecret string
|
||||
options.ServerFlags.StringVar(
|
||||
&githubSecret, "github-secret", "",
|
||||
"secret for github webhooks (same as GITHUB_SECRET=)",
|
||||
)
|
||||
webhooks.AddProvider("github", InitWebhook("github", &githubSecret, "GITHUB_SECRET"))
|
||||
}
|
||||
|
||||
func registerGithubish(providername string, secret *string, envname string) func() {
|
||||
func InitWebhook(providername string, secret *string, envname string) func() {
|
||||
return func() {
|
||||
if "" == *secret {
|
||||
*secret = os.Getenv(envname)
|
||||
|
@ -29,9 +33,9 @@ func registerGithubish(providername string, secret *string, envname string) func
|
|||
return
|
||||
}
|
||||
githubSecretB := []byte(*secret)
|
||||
webhooks[providername] = func(router chi.Router) {
|
||||
webhooks.AddRouteHandler(providername, func(router chi.Router) {
|
||||
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
body := http.MaxBytesReader(w, r.Body, maxBodySize)
|
||||
body := http.MaxBytesReader(w, r.Body, options.DefaultMaxBodySize)
|
||||
defer func() {
|
||||
_ = body.Close()
|
||||
}()
|
||||
|
@ -63,13 +67,13 @@ func registerGithubish(providername string, secret *string, envname string) func
|
|||
|
||||
ref := e.GetRef() // *e.Ref
|
||||
branch := ref[len("refs/heads/"):]
|
||||
hooks <- webhook{
|
||||
rev: e.GetAfter(), // *e.After
|
||||
ref: ref,
|
||||
branch: branch,
|
||||
repo: e.GetRepo().GetName(), // *e.Repo.Name
|
||||
org: e.GetRepo().GetOrganization(), // *e.Repo.Organization
|
||||
}
|
||||
webhooks.Hook(webhooks.Ref{
|
||||
Rev: e.GetAfter(), // *e.After
|
||||
Ref: ref,
|
||||
Branch: branch,
|
||||
Repo: e.GetRepo().GetName(), // *e.Repo.Name
|
||||
Org: e.GetRepo().GetOrganization(), // *e.Repo.Organization
|
||||
})
|
||||
/*
|
||||
case *github.PullRequestEvent:
|
||||
// probably doesn't matter
|
||||
|
@ -84,6 +88,6 @@ func registerGithubish(providername string, secret *string, envname string) func
|
|||
}
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
// Ref represents typical git webhook info such as:
|
||||
// HTTPSURL ex: https://git@git.example.com/example/example.git
|
||||
// SSHURL ex: ssh://git@git.example.com/example/example.git
|
||||
// Rev ex: 00000000
|
||||
// Ref ex: /refs/heads/master
|
||||
// Branch ex: master
|
||||
// Repo ex: example
|
||||
// Org ex: example
|
||||
type Ref struct {
|
||||
HTTPSURL string
|
||||
SSHURL string
|
||||
Rev string
|
||||
Ref string
|
||||
Branch string
|
||||
Repo string
|
||||
Org string
|
||||
}
|
||||
|
||||
var Providers = make(map[string]func())
|
||||
var Webhooks = make(map[string]func(chi.Router))
|
||||
|
||||
var hooks = make(chan Ref)
|
||||
|
||||
func Hook(r Ref) {
|
||||
hooks <- r
|
||||
}
|
||||
|
||||
func Accept() Ref {
|
||||
return <-hooks
|
||||
}
|
||||
|
||||
func AddProvider(name string, initProvider func()) {
|
||||
Providers[name] = initProvider
|
||||
}
|
||||
|
||||
func AddRouteHandler(name string, route func(router chi.Router)) {
|
||||
Webhooks[name] = route
|
||||
}
|
||||
|
||||
func MustRegisterAll() {
|
||||
for _, addHandler := range Providers {
|
||||
addHandler()
|
||||
}
|
||||
}
|
||||
|
||||
func RouteHandlers(r chi.Router) {
|
||||
r.Route("/api/webhooks", func(r chi.Router) {
|
||||
for provider, handler := range Webhooks {
|
||||
r.Route("/"+provider, func(r chi.Router) {
|
||||
handler(r)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
79
main.go
79
main.go
|
@ -9,6 +9,8 @@ import (
|
|||
"time"
|
||||
|
||||
"git.ryanburnette.com/ryanburnette/git-deploy/assets"
|
||||
"git.ryanburnette.com/ryanburnette/git-deploy/internal/options"
|
||||
"git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
|
@ -35,39 +37,18 @@ func ver() {
|
|||
fmt.Printf("%s v%s %s (%s)\n", name, version, commit[:7], date)
|
||||
}
|
||||
|
||||
type runOptions struct {
|
||||
listen string
|
||||
trustProxy bool
|
||||
compress bool
|
||||
static string
|
||||
}
|
||||
|
||||
var runOpts *options.ServerConfig
|
||||
var runFlags *flag.FlagSet
|
||||
var runOpts runOptions
|
||||
var initFlags *flag.FlagSet
|
||||
|
||||
var webhookProviders = make(map[string]func())
|
||||
var webhooks = make(map[string]func(chi.Router))
|
||||
var maxBodySize int64 = 1024 * 1024
|
||||
|
||||
var hooks chan webhook
|
||||
|
||||
type webhook struct {
|
||||
rev string
|
||||
ref string
|
||||
branch string
|
||||
repo string
|
||||
org string
|
||||
}
|
||||
|
||||
func init() {
|
||||
hooks = make(chan webhook)
|
||||
runOpts = runOptions{}
|
||||
runFlags = flag.NewFlagSet("run", flag.ExitOnError)
|
||||
runFlags.StringVar(&runOpts.listen, "listen", ":3000", "the address and port on which to listen")
|
||||
runFlags.BoolVar(&runOpts.trustProxy, "trust-proxy", false, "trust X-Forwarded-For header")
|
||||
runFlags.BoolVar(&runOpts.compress, "compress", true, "enable compression for text,html,js,css,etc")
|
||||
runFlags.StringVar(&runOpts.static, "serve-path", "", "path to serve, falls back to built-in web app")
|
||||
runOpts = options.Server
|
||||
runFlags = options.ServerFlags
|
||||
initFlags = options.InitFlags
|
||||
runFlags.StringVar(&runOpts.Addr, "listen", ":3000", "the address and port on which to listen")
|
||||
runFlags.BoolVar(&runOpts.TrustProxy, "trust-proxy", false, "trust X-Forwarded-For header")
|
||||
runFlags.BoolVar(&runOpts.Compress, "compress", true, "enable compression for text,html,js,css,etc")
|
||||
runFlags.StringVar(&runOpts.ServePath, "serve-path", "", "path to serve, falls back to built-in web app")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -99,7 +80,7 @@ func main() {
|
|||
initFlags.Parse(args[2:])
|
||||
case "run":
|
||||
runFlags.Parse(args[2:])
|
||||
registerWebhooks()
|
||||
webhooks.MustRegisterAll()
|
||||
serve()
|
||||
default:
|
||||
usage()
|
||||
|
@ -112,10 +93,10 @@ func serve() {
|
|||
r := chi.NewRouter()
|
||||
|
||||
// A good base middleware stack
|
||||
if runOpts.trustProxy {
|
||||
if runOpts.TrustProxy {
|
||||
r.Use(middleware.RealIP)
|
||||
}
|
||||
if runOpts.compress {
|
||||
if runOpts.Compress {
|
||||
r.Use(middleware.Compress(flate.DefaultCompression))
|
||||
}
|
||||
r.Use(middleware.Logger)
|
||||
|
@ -125,9 +106,9 @@ func serve() {
|
|||
var staticHandler http.HandlerFunc
|
||||
pub := http.FileServer(assets.Assets)
|
||||
|
||||
if len(runOpts.static) > 0 {
|
||||
if len(runOpts.ServePath) > 0 {
|
||||
// try the user-provided directory first, then fallback to the built-in
|
||||
devFS := http.Dir(runOpts.static)
|
||||
devFS := http.Dir(runOpts.ServePath)
|
||||
dev := http.FileServer(devFS)
|
||||
staticHandler = func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, err := devFS.Open(r.URL.Path); nil != err {
|
||||
|
@ -142,13 +123,13 @@ func serve() {
|
|||
}
|
||||
}
|
||||
|
||||
loadWebhooks(r)
|
||||
webhooks.RouteHandlers(r)
|
||||
|
||||
r.Get("/*", staticHandler)
|
||||
|
||||
fmt.Println("Listening for http (with reasonable timeouts) on", runOpts.listen)
|
||||
fmt.Println("Listening for http (with reasonable timeouts) on", runOpts.Addr)
|
||||
srv := &http.Server{
|
||||
Addr: runOpts.listen,
|
||||
Addr: runOpts.Addr,
|
||||
Handler: r,
|
||||
ReadHeaderTimeout: 2 * time.Second,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
|
@ -158,11 +139,11 @@ func serve() {
|
|||
|
||||
go func() {
|
||||
for {
|
||||
hook := <-hooks
|
||||
hook := webhooks.Accept()
|
||||
// TODO os.Exec
|
||||
fmt.Println(hook.org)
|
||||
fmt.Println(hook.repo)
|
||||
fmt.Println(hook.branch)
|
||||
fmt.Println(hook.Org)
|
||||
fmt.Println(hook.Repo)
|
||||
fmt.Println(hook.Branch)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -172,19 +153,3 @@ func serve() {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
func registerWebhooks() {
|
||||
for _, add := range webhookProviders {
|
||||
add()
|
||||
}
|
||||
}
|
||||
|
||||
func loadWebhooks(r chi.Router) {
|
||||
r.Route("/api/webhooks", func(r chi.Router) {
|
||||
for provider, handler := range webhooks {
|
||||
r.Route("/"+provider, func(r chi.Router) {
|
||||
handler(r)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue