-
-
Save BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a to your computer and use it in GitHub Desktop.
Basically this is a reverse proxy that automatically logs people (and more importantly, search engines) into my BuildMaster installation automatically.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FROM golang:latest | |
WORKDIR /go/src/gist.github.com/BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a.git | |
COPY . . | |
RUN CGO_ENABLED=0 go build -a -o buildmaster-proxy . | |
FROM scratch | |
COPY --from=0 /go/src/gist.github.com/BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a.git/buildmaster-proxy / | |
EXPOSE 8081 | |
CMD ["/buildmaster-proxy"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module gist.github.com/BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a.git | |
go 1.13 | |
require ( | |
github.com/davecgh/go-spew v1.1.1 | |
github.com/pkg/errors v0.8.1 | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | |
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"crypto/hmac" | |
"crypto/sha1" | |
"encoding/hex" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"net/http/httputil" | |
"net/url" | |
"os" | |
"strconv" | |
"strings" | |
"sync" | |
"time" | |
"github.com/davecgh/go-spew/spew" | |
"github.com/pkg/errors" | |
) | |
func main() { | |
proxyBuildMaster := &httputil.ReverseProxy{ | |
Director: BuildMaster.Director, | |
FlushInterval: 100 * time.Millisecond, | |
} | |
var mux http.ServeMux | |
mux.HandleFunc("/hooks/github", GitHubHook) | |
mux.Handle("/", proxyBuildMaster) | |
panic(http.ListenAndServe(":8081", &mux)) | |
} | |
var BuildMaster = &Proxy{ | |
Host: "192.168.1.100:8622", | |
LogInAPI: "BuildMaster.Web.WebApplication/Inedo.BuildMaster.Web.WebApplication.Pages.LogInPage/LogIn", | |
} | |
type Proxy struct { | |
Host string | |
LogInAPI string | |
} | |
func (p *Proxy) Director(req *http.Request) { | |
req.URL.Scheme = "http" | |
req.URL.Host = p.Host | |
req.Header.Set("User-Agent", req.Header.Get("User-Agent")+" (forwarded-for/"+req.Header.Get("X-Forwarded-For")+") (cloudflare-geo/"+req.Header.Get("CF-IPCountry")+")") | |
//log.Println("[proxy]", req.Method, req.URL.Path) | |
} | |
var hookSecret = []byte(os.Getenv("GITHUB_HOOK_SECRET")) | |
// Only one hook gets handled at a time. | |
var hookLock sync.Mutex | |
func GitHubHook(w http.ResponseWriter, r *http.Request) { | |
if !strings.HasPrefix(r.UserAgent(), "GitHub-Hookshot/") { | |
http.Error(w, "This is the GitHub hook endpoint.", http.StatusForbidden) | |
return | |
} | |
log.Println("[webhook]", r.Method, r.URL.Path) | |
signature, err := hex.DecodeString(strings.TrimPrefix(r.Header.Get("X-Hub-Signature"), "sha1=")) | |
if err != nil { | |
http.Error(w, "signature: "+err.Error(), http.StatusInternalServerError) | |
return | |
} | |
payloadBytes, err := ioutil.ReadAll(r.Body) | |
if err != nil { | |
http.Error(w, "body: "+err.Error(), http.StatusInternalServerError) | |
return | |
} | |
hash := hmac.New(sha1.New, hookSecret) | |
_, _ = hash.Write(payloadBytes) | |
if !hmac.Equal(signature, hash.Sum(nil)) { | |
http.Error(w, "Invalid signature.", http.StatusBadRequest) | |
return | |
} | |
hookLock.Lock() | |
defer hookLock.Unlock() | |
switch r.Header.Get("X-GitHub-Event") { | |
case "push": | |
var payload struct { | |
Ref string `json:"ref"` | |
After string `json:"after"` | |
Repository struct { | |
FullName string `json:"full_name"` | |
} `json:"repository"` | |
} | |
if err = json.Unmarshal(payloadBytes, &payload); err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
switch payload.Repository.FullName { | |
case "DFHack/dfhack": | |
if payload.Ref != "refs/heads/develop" { | |
break | |
} | |
CoreCommit(payload.After) | |
case "BenLubar/df-ai": | |
if payload.Ref != "refs/heads/master" { | |
break | |
} | |
PluginCommit("df-ai", payload.After) | |
case "BenLubar/weblegends": | |
if payload.Ref != "refs/heads/master" { | |
break | |
} | |
PluginCommit("weblegends", payload.After) | |
case "BenLubar/bingo": | |
if payload.Ref != "refs/heads/master" { | |
break | |
} | |
PluginCommit("bingo", payload.After) | |
} | |
case "create": | |
var payload struct { | |
RefType string `json:"ref_type"` | |
Ref string `json:"ref"` | |
Repository struct { | |
FullName string `json:"full_name"` | |
} `json:"repository"` | |
} | |
if err = json.Unmarshal(payloadBytes, &payload); err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
if payload.RefType == "tag" { | |
switch payload.Repository.FullName { | |
case "DFHack/dfhack": | |
CoreTag(payload.Ref) | |
case "BenLubar/df-ai": | |
PluginTag("BenLubar", "df-ai", payload.Ref) | |
case "BenLubar/weblegends": | |
PluginTag("BenLubar", "weblegends", payload.Ref) | |
case "BenLubar/bingo": | |
PluginTag("BenLubar", "bingo", payload.Ref) | |
} | |
} | |
case "pull_request": | |
var payload struct { | |
Action string `json:"action"` | |
Number int `json:"number"` | |
PullRequest PullRequest `json:"pull_request"` | |
} | |
if err = json.Unmarshal(payloadBytes, &payload); err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
var fn func(string, string, string, string) | |
switch payload.Action { | |
case "opened": | |
fn = payload.PullRequest.OnOpen | |
case "closed": | |
fn = payload.PullRequest.OnClose | |
case "reopened": | |
fn = payload.PullRequest.OnReopen | |
case "synchronize": | |
fn = payload.PullRequest.OnSynchronize | |
default: | |
w.WriteHeader(http.StatusNoContent) | |
return | |
} | |
var applicationName string | |
var pipelineName string | |
commitVariable := "$DFHackCommit" | |
expectedBranch := "master" | |
switch payload.PullRequest.Base.Repo.FullName { | |
case "DFHack/dfhack": | |
applicationName = "DFHack Pull Requests" | |
expectedBranch = "develop" | |
pipelineName = "dfhack-raft::DFHack-Core-Pull-Request" | |
case "DFHack/df-structures": | |
applicationName = "df-structures Pull Requests" | |
commitVariable = "$OverrideStructuresCommit" | |
pipelineName = "dfhack-raft::DFHack-Core-Pull-Request" | |
case "BenLubar/df-ai": | |
applicationName = "df-ai Pull Requests" | |
commitVariable = "$PluginCommit" | |
pipelineName = "dfhack-raft::DFHack-Plugin-Pull-Request" | |
case "BenLubar/weblegends": | |
applicationName = "weblegends Pull Requests" | |
commitVariable = "$PluginCommit" | |
pipelineName = "dfhack-raft::DFHack-Plugin-Pull-Request" | |
case "BenLubar/bingo": | |
applicationName = "bingo Pull Requests" | |
commitVariable = "$PluginCommit" | |
pipelineName = "dfhack-raft::DFHack-Plugin-Pull-Request" | |
default: | |
w.WriteHeader(http.StatusNoContent) | |
return | |
} | |
if payload.PullRequest.Base.Ref != expectedBranch && payload.Action != "closed" { | |
w.WriteHeader(http.StatusNoContent) | |
return | |
} | |
fn(pipelineName, applicationName, commitVariable, strconv.Itoa(payload.Number)) | |
} | |
w.WriteHeader(http.StatusNoContent) | |
} | |
func EnsureRelease(applicationName, releaseNumber string, payload url.Values) (interface{}, error) { | |
releases, err := APIRequest("/api/releases", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {releaseNumber}, | |
}) | |
if err != nil { | |
return nil, err | |
} | |
if r, ok := releases.([]interface{}); ok && len(r) != 0 { | |
return r[0], nil | |
} | |
payload.Set("applicationName", applicationName) | |
payload.Set("releaseNumber", releaseNumber) | |
return APIRequest("/api/releases/create", payload) | |
} | |
func APIRequest(path string, payload url.Values) (interface{}, error) { | |
payload.Set("key", os.Getenv("BUILDMASTER_API_KEY")) | |
r, err := http.Post("http://"+BuildMaster.Host+path, "application/x-www-form-urlencoded", strings.NewReader(payload.Encode())) | |
if err != nil { | |
return nil, errors.Wrap(err, "performing POST request") | |
} | |
defer r.Body.Close() | |
if r.StatusCode >= 400 { | |
body, err := ioutil.ReadAll(r.Body) | |
if err != nil { | |
return nil, errors.Wrapf(err, "reading HTTP %d response body", r.StatusCode) | |
} | |
return nil, errors.Errorf("HTTP %d error: %s", r.StatusCode, string(body)) | |
} | |
var v interface{} | |
err = json.NewDecoder(r.Body).Decode(&v) | |
return v, errors.Wrap(err, "decoding JSON body") | |
} | |
func CoreCommit(sha string) { | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"applicationName": {"DFHack"}, | |
"pipelineName": {"dfhack-raft::DFHack"}, | |
"$DFHackCommit": {sha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func GetLatestCommitOnBranch(branch, repo string) (string, error) { | |
r, err := http.Get("https://api.github.com/repos/" + repo + "/branches/" + branch) | |
if err != nil { | |
return "", err | |
} | |
defer r.Body.Close() | |
var data struct { | |
Commit struct { | |
Sha string `json:"sha"` | |
} `json:"commit"` | |
} | |
err = json.NewDecoder(r.Body).Decode(&data) | |
return data.Commit.Sha, err | |
} | |
func PluginCommit(plugin, sha string) { | |
coreSha, err := GetLatestCommitOnBranch("develop", "DFHack/dfhack") | |
if err != nil { | |
panic(err) | |
} | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"applicationName": {plugin}, | |
"pipelineName": {"dfhack-raft::DFHack-Plugin"}, | |
"$DFHackCommit": {coreSha}, | |
"$PluginCommit": {sha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
coreTag, coreSha, err := GetTag("", "DFHack/dfhack") | |
if err != nil { | |
panic(err) | |
} | |
response, err = APIRequest("/api/releases/packages/create", url.Values{ | |
"applicationName": {plugin}, | |
"releaseNumber": {"dfhack-" + coreTag}, | |
"$DFHackCommit": {coreSha}, | |
"$PluginCommit": {sha}, | |
}) | |
if err != nil { | |
if err.Error() == "Release dfhack-"+coreTag+" for application "+plugin+" is not an active release or does not exist." { | |
return | |
} | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func DeployReleases(application string) { | |
releases, err := APIRequest("/api/releases", url.Values{ | |
"applicationName": {application}, | |
"status": {"active"}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
for _, release := range releases.([]interface{}) { | |
data := release.(map[string]interface{}) | |
num := data["number"].(string) | |
if num == "0.0.0" { | |
// Don't deploy legacy develop release | |
continue | |
} | |
if strings.HasPrefix(num, "dfhack-") { | |
// Don't deploy prereleases | |
continue | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(data["latestPackageId"])}, | |
"toStage": {"Deployed"}, | |
"force": {"true"}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
} | |
func GetTag(expectedName, repo string) (name, sha string, err error) { | |
r, err := http.Get("https://api.github.com/repos/" + repo + "/tags") | |
if err != nil { | |
return "", "", err | |
} | |
defer r.Body.Close() | |
var tags []struct { | |
Name string `json:"name"` | |
Commit struct { | |
Sha string `json:"sha"` | |
} `json:"commit"` | |
} | |
err = json.NewDecoder(r.Body).Decode(&tags) | |
if err != nil { | |
return "", "", err | |
} | |
for _, tag := range tags { | |
if tag.Name == expectedName { | |
return tag.Name, tag.Commit.Sha, nil | |
} | |
} | |
return tags[0].Name, tags[0].Commit.Sha, nil | |
} | |
func CoreTag(tag string) { | |
DeployReleases("DFHack") | |
_, coreSha, err := GetTag(tag, "DFHack/dfhack") | |
if err != nil { | |
panic(err) | |
} | |
release, err := EnsureRelease("DFHack", tag, url.Values{ | |
"pipelineName": {"dfhack-raft::DFHack"}, | |
"$DFHackBranch": {"develop"}, | |
"$DFHackTag": {tag}, | |
"$DFHackTagged": {"true"}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"releaseId": {fmt.Sprint(release.(map[string]interface{})["id"])}, | |
"$DFHackCommit": {coreSha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func PluginTag(owner, plugin, tag string) { | |
DeployReleases(plugin) | |
coreTag, coreSha, err := GetTag("", "DFHack/dfhack") | |
if err != nil { | |
panic(err) | |
} | |
_, pluginSha, err := GetTag(tag, owner+"/"+plugin) | |
if err != nil { | |
panic(err) | |
} | |
release, err := EnsureRelease(plugin, tag, url.Values{ | |
"pipelineName": {"dfhack-raft::DFHack-Plugin"}, | |
"$DFHackBranch": {"develop"}, | |
"$DFHackTag": {coreTag}, | |
"$PluginTag": {tag}, | |
"$PluginTagged": {"true"}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"releaseId": {fmt.Sprint(release.(map[string]interface{})["id"])}, | |
"$DFHackCommit": {coreSha}, | |
"$PluginCommit": {pluginSha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
type PullRequest struct { | |
Body string `json:"body"` | |
Head struct { | |
Sha string `json:"sha"` | |
Repo struct { | |
Name string `json:"name"` | |
Owner struct { | |
Login string `json:"login"` | |
} `json:"owner"` | |
} `json:"repo"` | |
} `json:"head"` | |
Base struct { | |
Ref string `json:"ref"` | |
Repo struct { | |
FullName string `json:"full_name"` | |
} `json:"repo"` | |
} `json:"base"` | |
Merged bool `json:"merged"` | |
} | |
func (pr *PullRequest) OnOpen(pipelineName, applicationName, commitVariable, number string) { | |
if strings.Contains(pr.Body, "buildmaster:skip-pr") { | |
return | |
} | |
release, err := EnsureRelease(applicationName, number, url.Values{ | |
"pipelineName": {pipelineName}, | |
"$ForkOwner": {pr.Head.Repo.Owner.Login}, | |
"$ForkRepo": {pr.Head.Repo.Name}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"releaseId": {fmt.Sprint(release.(map[string]interface{})["id"])}, | |
commitVariable: {pr.Head.Sha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
log.Println(err) | |
} | |
} | |
func (pr *PullRequest) OnClose(pipelineName, applicationName, commitVariable, number string) { | |
if pr.Merged { | |
releases, err := APIRequest("/api/releases", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {number}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
for _, release := range releases.([]interface{}) { | |
spew.Dump(release) | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"buildId": {fmt.Sprint(release.(map[string]interface{})["latestPackageId"])}, | |
"toStage": {"Merged"}, | |
"force": {"true"}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
} else { | |
_, err := APIRequest("/api/releases/cancel", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {number}, | |
}) | |
if err != nil { | |
log.Println(err) | |
} | |
} | |
} | |
func (pr *PullRequest) OnReopen(pipelineName, applicationName, commitVariable, number string) { | |
_, err := APIRequest("/api/releases/restore", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {number}, | |
}) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {number}, | |
commitVariable: {pr.Head.Sha}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} | |
func (pr *PullRequest) OnSynchronize(pipelineName, applicationName, commitVariable, number string) { | |
response, err := APIRequest("/api/releases/packages/create", url.Values{ | |
"applicationName": {applicationName}, | |
"releaseNumber": {number}, | |
commitVariable: {pr.Head.Sha}, | |
}) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
_, err = APIRequest("/api/releases/packages/deploy", url.Values{ | |
"packageId": {fmt.Sprint(response.(map[string]interface{})["id"])}, | |
}) | |
if err != nil { | |
panic(err) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment