Browse Source

refactor webhooks

master
AJ ONeal 4 years ago
parent
commit
f4cda06e64
  1. 58
      README.md
  2. 8
      github.go
  3. 23
      internal/options/options.go
  4. 38
      internal/webhooks/github/github.go
  5. 60
      internal/webhooks/webhooks.go
  6. 79
      main.go

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.

8
github.go

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

23
internal/options/options.go

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

38
webhooks_github.go → internal/webhooks/github/github.go

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

60
internal/webhooks/webhooks.go

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

@ -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…
Cancel
Save