add bitbucket, update build tags
This commit is contained in:
parent
cad179977c
commit
c07056d9fd
11
README.md
11
README.md
|
@ -69,6 +69,17 @@ Just the `push` event.
|
||||||
Active: ✅
|
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
|
## 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
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !nobitbucket
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks/bitbucket"
|
||||||
|
)
|
|
@ -1,5 +1,4 @@
|
||||||
// // +build github
|
// +build !nogithub
|
||||||
// TODO omit github unless specified by build tag
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
}
|
|
@ -12,6 +12,8 @@ import (
|
||||||
"git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks"
|
"git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"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"
|
"github.com/google/go-github/v32/github"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
6
main.go
6
main.go
|
@ -93,9 +93,9 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return
|
return
|
||||||
case "init":
|
case "init":
|
||||||
initFlags.Parse(args[2:])
|
_ = initFlags.Parse(args[2:])
|
||||||
case "run":
|
case "run":
|
||||||
runFlags.Parse(args[2:])
|
_ = runFlags.Parse(args[2:])
|
||||||
if "" == runOpts.Exec {
|
if "" == runOpts.Exec {
|
||||||
fmt.Printf("--exec <path/to/script.sh> is a required flag")
|
fmt.Printf("--exec <path/to/script.sh> is a required flag")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -210,7 +210,7 @@ func serve() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Printf("git-deploy job for %s#%s started\n", hook.HTTPSURL, hook.RefName)
|
log.Printf("git-deploy job for %s#%s started\n", hook.HTTPSURL, hook.RefName)
|
||||||
cmd.Wait()
|
_ = cmd.Wait()
|
||||||
delete(jobs, jobID)
|
delete(jobs, jobID)
|
||||||
log.Printf("git-deploy job for %s#%s finished\n", hook.HTTPSURL, hook.RefName)
|
log.Printf("git-deploy job for %s#%s finished\n", hook.HTTPSURL, hook.RefName)
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Reference in New Issue