refactor webhooks

This commit is contained in:
AJ ONeal 2020-09-28 17:39:05 -06:00
parent 3011e337ec
commit f4cda06e64
6 changed files with 192 additions and 74 deletions

View File

@ -22,6 +22,31 @@ go build -mod=vendor .
./git-deploy run --listen :3000 --serve-path ./overrides ./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 ## TODO
**git-deploy** is intended for use with static websites that are generated after **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 don't want to use it. The built-in interface requires the built-in
authentication. 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 ## License
Copyright 2020. All rights reserved. Copyright 2020. All rights reserved.

8
github.go Normal file
View File

@ -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"
)

View File

@ -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)
}

View File

@ -1,6 +1,4 @@
// // +build github package github
package main
import ( import (
"fmt" "fmt"
@ -9,17 +7,23 @@ import (
"net/http" "net/http"
"os" "os"
"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"
"github.com/google/go-github/v32/github" "github.com/google/go-github/v32/github"
) )
func init() { func init() {
githubSecret := "" var githubSecret string
runFlags.StringVar(&githubSecret, "github-secret", "", "secret for github webhooks (same as GITHUB_SECRET=)") options.ServerFlags.StringVar(
webhookProviders["github"] = registerGithubish("github", &githubSecret, "GITHUB_SECRET") &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() { return func() {
if "" == *secret { if "" == *secret {
*secret = os.Getenv(envname) *secret = os.Getenv(envname)
@ -29,9 +33,9 @@ func registerGithubish(providername string, secret *string, envname string) func
return return
} }
githubSecretB := []byte(*secret) 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) { 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() { defer func() {
_ = body.Close() _ = body.Close()
}() }()
@ -63,13 +67,13 @@ func registerGithubish(providername string, secret *string, envname string) func
ref := e.GetRef() // *e.Ref ref := e.GetRef() // *e.Ref
branch := ref[len("refs/heads/"):] branch := ref[len("refs/heads/"):]
hooks <- webhook{ webhooks.Hook(webhooks.Ref{
rev: e.GetAfter(), // *e.After Rev: e.GetAfter(), // *e.After
ref: ref, Ref: ref,
branch: branch, Branch: branch,
repo: e.GetRepo().GetName(), // *e.Repo.Name Repo: e.GetRepo().GetName(), // *e.Repo.Name
org: e.GetRepo().GetOrganization(), // *e.Repo.Organization Org: e.GetRepo().GetOrganization(), // *e.Repo.Organization
} })
/* /*
case *github.PullRequestEvent: case *github.PullRequestEvent:
// probably doesn't matter // probably doesn't matter
@ -84,6 +88,6 @@ func registerGithubish(providername string, secret *string, envname string) func
} }
}) })
} })
} }
} }

View File

@ -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
View File

@ -9,6 +9,8 @@ import (
"time" "time"
"git.ryanburnette.com/ryanburnette/git-deploy/assets" "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"
"github.com/go-chi/chi/middleware" "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) fmt.Printf("%s v%s %s (%s)\n", name, version, commit[:7], date)
} }
type runOptions struct { var runOpts *options.ServerConfig
listen string
trustProxy bool
compress bool
static string
}
var runFlags *flag.FlagSet var runFlags *flag.FlagSet
var runOpts runOptions
var initFlags *flag.FlagSet 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() { func init() {
hooks = make(chan webhook) runOpts = options.Server
runOpts = runOptions{} runFlags = options.ServerFlags
runFlags = flag.NewFlagSet("run", flag.ExitOnError) initFlags = options.InitFlags
runFlags.StringVar(&runOpts.listen, "listen", ":3000", "the address and port on which to listen") 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.TrustProxy, "trust-proxy", false, "trust X-Forwarded-For header")
runFlags.BoolVar(&runOpts.compress, "compress", true, "enable compression for text,html,js,css,etc") 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") runFlags.StringVar(&runOpts.ServePath, "serve-path", "", "path to serve, falls back to built-in web app")
} }
func main() { func main() {
@ -99,7 +80,7 @@ func main() {
initFlags.Parse(args[2:]) initFlags.Parse(args[2:])
case "run": case "run":
runFlags.Parse(args[2:]) runFlags.Parse(args[2:])
registerWebhooks() webhooks.MustRegisterAll()
serve() serve()
default: default:
usage() usage()
@ -112,10 +93,10 @@ func serve() {
r := chi.NewRouter() r := chi.NewRouter()
// A good base middleware stack // A good base middleware stack
if runOpts.trustProxy { if runOpts.TrustProxy {
r.Use(middleware.RealIP) r.Use(middleware.RealIP)
} }
if runOpts.compress { if runOpts.Compress {
r.Use(middleware.Compress(flate.DefaultCompression)) r.Use(middleware.Compress(flate.DefaultCompression))
} }
r.Use(middleware.Logger) r.Use(middleware.Logger)
@ -125,9 +106,9 @@ func serve() {
var staticHandler http.HandlerFunc var staticHandler http.HandlerFunc
pub := http.FileServer(assets.Assets) 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 // 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) dev := http.FileServer(devFS)
staticHandler = func(w http.ResponseWriter, r *http.Request) { staticHandler = func(w http.ResponseWriter, r *http.Request) {
if _, err := devFS.Open(r.URL.Path); nil != err { if _, err := devFS.Open(r.URL.Path); nil != err {
@ -142,13 +123,13 @@ func serve() {
} }
} }
loadWebhooks(r) webhooks.RouteHandlers(r)
r.Get("/*", staticHandler) 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{ srv := &http.Server{
Addr: runOpts.listen, Addr: runOpts.Addr,
Handler: r, Handler: r,
ReadHeaderTimeout: 2 * time.Second, ReadHeaderTimeout: 2 * time.Second,
ReadTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second,
@ -158,11 +139,11 @@ func serve() {
go func() { go func() {
for { for {
hook := <-hooks hook := webhooks.Accept()
// TODO os.Exec // TODO os.Exec
fmt.Println(hook.org) fmt.Println(hook.Org)
fmt.Println(hook.repo) fmt.Println(hook.Repo)
fmt.Println(hook.branch) fmt.Println(hook.Branch)
} }
}() }()
@ -172,19 +153,3 @@ func serve() {
return 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)
})
}
})
}