add bitbucket, update build tags

This commit is contained in:
AJ ONeal 2020-09-29 02:08:35 -06:00
parent cad179977c
commit c07056d9fd
7 changed files with 413 additions and 5 deletions

View File

@ -69,6 +69,17 @@ Just the `push` event.
Active: ✅
```
### Bitbucket
Sometimes Bitbucket does not give you the option to specify the (`X-Hub-Signature`) `secret`,
so you'll have to append an `access_token` instead. Example:
```txt
Title: git-deploy
URL: https://YOUR_DOMAIN/api/webhooks/bitbucket?access_token=YOUR_SECRET
Triggers: Repository push
```
## TODO
**git-deploy** is intended for use with static websites that are generated after

7
bitbucket.go Normal file
View File

@ -0,0 +1,7 @@
// +build !nobitbucket
package main
import (
_ "git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks/bitbucket"
)

View File

@ -1,5 +1,4 @@
// // +build github
// TODO omit github unless specified by build tag
// +build !nogithub
package main

View File

@ -0,0 +1,144 @@
package bitbucket
import (
"crypto/subtle"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"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() {
var secret string
name := "bitbucket"
options.ServerFlags.StringVar(
&secret, fmt.Sprintf("%s-secret", name), "",
fmt.Sprintf(
"secret for %s webhooks (same as %s_SECRET=)",
name, strings.ToUpper(name)),
)
webhooks.AddProvider("bitbucket", InitWebhook("bitbucket", &secret, "BITBUCKET_SECRET"))
}
// InitWebhook prepares the webhook router.
// It should be called after arguments are parsed and ENVs are set.InitWebhook
func InitWebhook(providername string, secret *string, envname string) func() {
return func() {
if "" == *secret {
*secret = os.Getenv(envname)
}
if "" == *secret {
fmt.Fprintf(os.Stderr, "skipped route for missing %s\n", envname)
return
}
secretB := []byte(*secret)
webhooks.AddRouteHandler(providername, func(router chi.Router) {
router.Post("/", func(w http.ResponseWriter, r *http.Request) {
body := http.MaxBytesReader(w, r.Body, options.DefaultMaxBodySize)
defer func() {
_ = body.Close()
}()
accessToken := r.URL.Query().Get("access_token")
if "" != accessToken {
if 0 == subtle.ConstantTimeCompare(
[]byte(r.URL.Query().Get("access_token")),
secretB,
) {
log.Printf("invalid bitbucket access_token\n")
http.Error(w, "invalid access_token", http.StatusBadRequest)
return
}
}
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
// if there's a read error, it should have been handled
// already by the MaxBytesReader
return
}
if "" == accessToken {
sig := r.Header.Get("X-Hub-Signature")
// TODO replace with generic X-Hub-Signature validation
if err := github.ValidateSignature(sig, payload, secretB); nil != err {
log.Printf("invalid bitbucket signature: error: %s\n", err)
http.Error(w, "invalid bitbucket signature", http.StatusBadRequest)
return
}
}
info := Webhook{}
if err := json.Unmarshal(payload, &info); nil != err {
log.Printf("invalid bitbucket payload: error: %s\n%s\n", err, string(payload))
http.Error(w, "invalid bitbucket payload", http.StatusBadRequest)
return
}
var branch string
var tag string
var ref string
n := len(info.Push.Changes)
if n < 1 {
log.Printf("invalid bitbucket changeset (n): %d\n%s\n", n, string(payload))
http.Error(w, "invalid bitbucket payload", http.StatusBadRequest)
return
} else if n > 1 {
log.Printf("more than one bitbucket changeset (n): %d\n%s\n", n, string(payload))
}
refName := info.Push.Changes[0].New.Name
refType := info.Push.Changes[0].New.Type
switch refType {
case "tag":
tag = refName
ref = fmt.Sprintf("refs/tags/%s", refName)
case "branch":
branch = refName
ref = fmt.Sprintf("refs/heads/%s", refName)
default:
log.Println("unexpected bitbucket RefType", refType)
ref = fmt.Sprintf("refs/UNKNOWN/%s", refName)
}
switch refType {
case "tags":
refType = "tag"
tag = refName
case "heads":
refType = "branch"
branch = refName
}
var rev string
if len(info.Push.Changes[0].Commits) > 0 {
// TODO first or last?
// TODO shouldn't tags have a Commit as well?
rev = info.Push.Changes[0].Commits[0].Hash
}
webhooks.Hook(webhooks.Ref{
HTTPSURL: info.Repository.Links.HTML.Href,
Rev: rev,
Ref: ref,
RefType: refType,
RefName: refName,
Branch: branch,
Tag: tag,
Repo: info.Repository.Name,
Owner: info.Repository.Workspace.Slug,
})
})
})
}
}

View File

@ -0,0 +1,245 @@
package bitbucket
import "time"
// Thank you Matt!
// See https://mholt.github.io/json-to-go/
type Webhook struct {
Push Push `json:"push"`
Actor Actor `json:"actor"`
Repository Repository `json:"repository"`
}
type Push struct {
Changes []struct {
Forced bool `json:"forced"`
Old struct {
Name string `json:"name"`
Type string `json:"type"`
Target struct {
Hash string `json:"hash"`
Author struct {
User struct {
DisplayName string `json:"display_name"`
UUID string `json:"uuid"`
Nickname string `json:"nickname"`
AccountID string `json:"account_id"`
} `json:"user"`
} `json:"author"`
Date time.Time `json:"date"`
Message string `json:"message"`
Type string `json:"type"`
} `json:"target"`
} `json:"old"`
Links struct {
HTML struct {
Href string `json:"href"`
} `json:"html"`
} `json:"links"`
Created bool `json:"created"`
Commits []struct {
Rendered struct {
} `json:"rendered"`
Hash string `json:"hash"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
Comments struct {
Href string `json:"href"`
} `json:"comments"`
Patch struct {
Href string `json:"href"`
} `json:"patch"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
Diff struct {
Href string `json:"href"`
} `json:"diff"`
Approve struct {
Href string `json:"href"`
} `json:"approve"`
Statuses struct {
Href string `json:"href"`
} `json:"statuses"`
} `json:"links"`
Author struct {
Raw string `json:"raw"`
Type string `json:"type"`
User struct {
DisplayName string `json:"display_name"`
UUID string `json:"uuid"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
Avatar struct {
Href string `json:"href"`
} `json:"avatar"`
} `json:"links"`
Nickname string `json:"nickname"`
Type string `json:"type"`
AccountID string `json:"account_id"`
} `json:"user"`
} `json:"author"`
Summary struct {
Raw string `json:"raw"`
Markup string `json:"markup"`
HTML string `json:"html"`
Type string `json:"type"`
} `json:"summary"`
Parents []struct {
Hash string `json:"hash"`
Type string `json:"type"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
} `json:"links"`
} `json:"parents"`
Date time.Time `json:"date"`
Message string `json:"message"`
Type string `json:"type"`
Properties struct {
} `json:"properties"`
} `json:"commits"`
Truncated bool `json:"truncated"`
Closed bool `json:"closed"`
New struct {
Name string `json:"name"`
Links struct {
Commits struct {
Href string `json:"href"`
} `json:"commits"`
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
} `json:"links"`
DefaultMergeStrategy string `json:"default_merge_strategy"`
MergeStrategies []string `json:"merge_strategies"`
Type string `json:"type"`
Target struct {
Rendered struct {
} `json:"rendered"`
Hash string `json:"hash"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
} `json:"links"`
Author struct {
Raw string `json:"raw"`
Type string `json:"type"`
User struct {
DisplayName string `json:"display_name"`
UUID string `json:"uuid"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
Avatar struct {
Href string `json:"href"`
} `json:"avatar"`
} `json:"links"`
Nickname string `json:"nickname"`
Type string `json:"type"`
AccountID string `json:"account_id"`
} `json:"user"`
} `json:"author"`
Summary struct {
Raw string `json:"raw"`
Markup string `json:"markup"`
HTML string `json:"html"`
Type string `json:"type"`
} `json:"summary"`
Parents []struct {
Hash string `json:"hash"`
Type string `json:"type"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
} `json:"links"`
} `json:"parents"`
Date time.Time `json:"date"`
Message string `json:"message"`
Type string `json:"type"`
Properties struct {
} `json:"properties"`
} `json:"target"`
} `json:"new"`
} `json:"changes"`
}
type Actor struct {
DisplayName string `json:"display_name"`
UUID string `json:"uuid"`
Nickname string `json:"nickname"`
Type string `json:"type"`
AccountID string `json:"account_id"`
}
type Repository struct {
Name string `json:"name"`
Scm string `json:"scm"`
Website interface{} `json:"website"`
UUID string `json:"uuid"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
Avatar struct {
Href string `json:"href"`
} `json:"avatar"`
} `json:"links"`
FullName string `json:"full_name"`
Owner struct {
DisplayName string `json:"display_name"`
UUID string `json:"uuid"`
Links struct {
Self struct {
Href string `json:"href"`
} `json:"self"`
HTML struct {
Href string `json:"href"`
} `json:"html"`
Avatar struct {
Href string `json:"href"`
} `json:"avatar"`
} `json:"links"`
Nickname string `json:"nickname"`
Type string `json:"type"`
AccountID string `json:"account_id"`
} `json:"owner"`
Workspace struct {
Slug string `json:"slug"`
Type string `json:"type"`
Name string `json:"name"`
UUID string `json:"uuid"`
} `json:"workspace"`
Type string `json:"type"`
IsPrivate bool `json:"is_private"`
}

View File

@ -12,6 +12,8 @@ import (
"git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks"
"github.com/go-chi/chi"
// TODO nix this dependency in favor of a lightweight X-Hub-Signature
// and JSON-to-Go-struct approach
"github.com/google/go-github/v32/github"
)

View File

@ -93,9 +93,9 @@ func main() {
os.Exit(0)
return
case "init":
initFlags.Parse(args[2:])
_ = initFlags.Parse(args[2:])
case "run":
runFlags.Parse(args[2:])
_ = runFlags.Parse(args[2:])
if "" == runOpts.Exec {
fmt.Printf("--exec <path/to/script.sh> is a required flag")
os.Exit(1)
@ -210,7 +210,7 @@ func serve() {
go func() {
log.Printf("git-deploy job for %s#%s started\n", hook.HTTPSURL, hook.RefName)
cmd.Wait()
_ = cmd.Wait()
delete(jobs, jobID)
log.Printf("git-deploy job for %s#%s finished\n", hook.HTTPSURL, hook.RefName)
}()