gitdeploy/internal/webhooks/webhooks.go

200 lines
4.9 KiB
Go

// Package webhooks provides the data structures and utilities related to the hooks
package webhooks
import (
"encoding/base64"
"os"
"strings"
"time"
"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 {
RepoID string `json:"repo_id"`
Timestamp time.Time `json:"timestamp"`
HTTPSURL string `json:"https_url"`
SSHURL string `json:"ssh_url"`
Rev string `json:"rev"`
Ref string `json:"ref"` // refs/tags/v0.0.1, refs/heads/master
RefType string `json:"ref_type"` // tag, branch
RefName string `json:"ref_name"`
Owner string `json:"repo_owner"`
Repo string `json:"repo_name"`
//Branch string `json:"branch"` // deprecated
//Tag string `json:"tag"` // deprecated
}
// RefID is a newtype string
type RefID string
// URLSafeRefID is a newtype string
type URLSafeRefID string
// RevID is a newtype string
type RevID string
// URLSafeRevID is a newtype string
type URLSafeRevID string
// URLSafeGitID is a newtype string
type URLSafeGitID string
// New returns a normalized Ref (Git reference)
func New(r Ref) *Ref {
if len(r.HTTPSURL) > 0 {
r.RepoID = getRepoID(r.HTTPSURL)
} else /*if len(r.SSHURL) > 0*/ {
r.RepoID = getRepoID(r.SSHURL)
}
r.Timestamp = getTimestamp(r.Timestamp)
return &r
}
// String prints object as git.example.com#branch@rev
func (h *Ref) String() string {
rev := h.Rev
if len(rev) > 7 {
rev = rev[:7]
}
return string(h.GetRefID()) + "@" + rev
}
// GetRefID returns a unique reference like "github.com/org/project#branch"
func (h *Ref) GetRefID() RefID {
return RefID(h.RepoID + "#" + h.RefName)
}
// GetURLSafeRefID returns the URL-safe Base64 encoding of the RefID
func (h *Ref) GetURLSafeRefID() URLSafeRefID {
return URLSafeRefID(
base64.RawURLEncoding.EncodeToString(
[]byte(h.GetRefID()),
),
)
}
// GetRevID returns a unique reference like "github.com/org/project#abcd7890"
func (h *Ref) GetRevID() RevID {
rev := h.Rev
if len(rev) > 7 {
rev = rev[:7]
}
return RevID(h.RepoID + "#" + rev)
}
// GetURLSafeRevID returns the URL-safe Base64 encoding of the RevID
func (h *Ref) GetURLSafeRevID() URLSafeRevID {
return URLSafeRevID(
base64.RawURLEncoding.EncodeToString(
[]byte(h.GetRevID()),
),
)
}
// Providers is a map of the git webhook providers
var Providers = make(map[string]func())
// Webhooks is a map of routes
var Webhooks = make(map[string]func(chi.Router))
// Hooks is a channel of incoming webhooks
var Hooks = make(chan Ref)
// Hook will put a Git Ref on the queue
func Hook(r Ref) {
Hooks <- r
}
// Accept will pop a Git Ref off the queue
func Accept() Ref {
return <-Hooks
}
// AddProvider registers a git webhook provider
func AddProvider(name string, initProvider func()) {
Providers[name] = initProvider
}
// AddRouteHandler registers a git webhook route
func AddRouteHandler(name string, route func(router chi.Router)) {
Webhooks[name] = route
}
// MustRegisterAll registers all webhook route functions
func MustRegisterAll() {
for _, addHandler := range Providers {
addHandler()
}
}
// RouteHandlers registers the webhook functions to the route
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)
})
}
})
}
// ParseSecrets grabs secrets from the ENV at runtime
func ParseSecrets(providername, secretList, envname string) [][]byte {
if 0 == len(secretList) {
secretList = os.Getenv(envname)
}
if 0 == len(secretList) {
return nil
}
var secrets [][]byte
for _, secret := range strings.Fields(strings.ReplaceAll(secretList, ",", " ")) {
if len(secret) > 0 {
secrets = append(secrets, []byte(secret))
}
}
return secrets
}
// https://git.example.com/example/project.git
// => git.example.com/example/project
func getRepoID(url string) string {
repoID := strings.TrimPrefix(url, "https://")
repoID = strings.TrimPrefix(repoID, "http://")
repoID = strings.TrimPrefix(repoID, "ssh://")
// "gitea@example.com:my-org/my-project" // removes gitea@
// "git/ea@example.com:my-org/my-project" // no change
// "gitea@" // empty string
firstSlash := strings.Index(repoID, "/")
firstAt := strings.Index(repoID, "@")
if firstSlash < 0 {
firstSlash = 999999
}
if firstAt >= 0 && firstAt < firstSlash && len(repoID) >= firstAt {
repoID = repoID[firstAt+1:]
repoID = strings.Replace(repoID, ":", "/", 1)
}
repoID = strings.TrimSuffix(repoID, ".git")
return repoID
}
func getTimestamp(t time.Time) time.Time {
if t.IsZero() {
t = time.Now().UTC()
}
return t
}