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
|
./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.
|
||||||
|
|
|
@ -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 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
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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"
|
"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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue