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: ✅ | ||||
| ``` | ||||
| 
 | ||||
| ### 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
									
								
							
							
						
						
									
										7
									
								
								bitbucket.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| // +build !nobitbucket | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	_ "git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks/bitbucket" | ||||
| ) | ||||
| @ -1,5 +1,4 @@ | ||||
| // // +build github | ||||
| // TODO omit github unless specified by build tag | ||||
| // +build !nogithub | ||||
| 
 | ||||
| package main | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										144
									
								
								internal/webhooks/bitbucket/bitbucket.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								internal/webhooks/bitbucket/bitbucket.go
									
									
									
									
									
										Normal 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, | ||||
| 				}) | ||||
| 			}) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										245
									
								
								internal/webhooks/bitbucket/payload.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								internal/webhooks/bitbucket/payload.go
									
									
									
									
									
										Normal 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"` | ||||
| } | ||||
| @ -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" | ||||
| ) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								main.go
									
									
									
									
									
								
							| @ -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) | ||||
| 			}() | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user