Skip to content

Instantly share code, notes, and snippets.

@BenLubar
Last active January 8, 2020 20:17
Show Gist options
  • Save BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a to your computer and use it in GitHub Desktop.
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.
FROM golang:latest
WORKDIR /go/src/gist.github.com/BenLubar/a10d4b0890d9d5894ec4c9bb0d35258a.git
COPY main.go .
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
EXPOSE 8628
CMD ["/buildmaster-proxy"]
package main
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"sync"
"time"
)
func main() {
proxyBuildMaster := &httputil.ReverseProxy{
Director: BuildMaster.Director,
FlushInterval: 100 * time.Millisecond,
ModifyResponse: BuildMaster.ModifyResponse,
}
proxyHedgehog := &httputil.ReverseProxy{
Director: Hedgehog.Director,
FlushInterval: 100 * time.Millisecond,
ModifyResponse: Hedgehog.ModifyResponse,
}
ch := make(chan error, 2)
go func() {
var mux http.ServeMux
mux.HandleFunc("/hooks/github", GitHubHook)
mux.Handle("/", proxyBuildMaster)
ch <- http.ListenAndServe(":8081", &mux)
}()
go func() {
ch <- http.ListenAndServe(":8628", proxyHedgehog)
}()
panic(<-ch)
}
var BuildMaster = &Proxy{
Host: "192.168.1.100:81",
LogInAPI: "BuildMaster.Web.WebApplication/Inedo.BuildMaster.Web.WebApplication.Pages.LogInPage/LogIn",
}
var Hedgehog = &Proxy{
Host: "192.168.1.100:8628",
LogInAPI: "Hedgehog.WebApplication/Inedo.Hedgehog.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
if _, ok := req.Header["User-Agent"]; !ok {
// explicitly disable User-Agent so it's not set to default value
req.Header.Set("User-Agent", "")
}
}
func (p *Proxy) ModifyResponse(res *http.Response) error {
if res.StatusCode != http.StatusFound {
return nil
}
loc := res.Header.Get("Location")
if loc == "/log-in" && len(res.Cookies()) == 0 {
loc = "/log-in?ReturnUrl=%2f"
}
if !strings.HasPrefix(loc, "/log-in?ReturnUrl=") {
return nil
}
loc, err := url.QueryUnescape(strings.TrimPrefix(loc, "/log-in?ReturnUrl="))
if err != nil || !strings.HasPrefix(loc, "/") || strings.HasPrefix(loc, "/0x44/") {
return nil
}
if err = res.Body.Close(); err != nil {
return err
}
req1, err := http.NewRequest(http.MethodPost, "http://"+p.Host+"/0x44/"+p.LogInAPI, strings.NewReader("userName=guest&password=hunter2"))
if err != nil {
return err
}
req1.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
res1, err := http.DefaultTransport.RoundTrip(req1)
if err != nil {
return err
}
err = res1.Body.Close()
if err != nil {
return err
}
if res1.StatusCode != http.StatusOK {
return errors.New("server returned status for LogIn: " + res1.Status)
}
if len(res1.Cookies()) != 1 {
return errors.New("server returned wrong number of cookies for LogIn")
}
auth := res1.Cookies()[0]
req2, err := http.NewRequest(http.MethodGet, "http://"+p.Host+loc, nil)
if err != nil {
return err
}
req2.AddCookie(auth)
res2, err := http.DefaultTransport.RoundTrip(req2)
if err != nil {
return err
}
res2.Header.Add("Set-Cookie", auth.String())
*res = *res2
return nil
}
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
}
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"`
Head string `json:"head"`
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.Head)
case "BenLubar/df-ai":
if payload.Ref != "refs/heads/master" {
break
}
PluginCommit("df-ai", payload.Head)
case "BenLubar/weblegends":
if payload.Ref != "refs/heads/master" {
break
}
PluginCommit("weblegends", payload.Head)
}
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)
}
}
}
w.WriteHeader(http.StatusNoContent)
}
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, err
}
defer r.Body.Close()
if r.StatusCode >= 400 {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
return nil, errors.New(string(body))
}
var v interface{}
err = json.NewDecoder(r.Body).Decode(&v)
return v, err
}
func CoreCommit(sha string) {
response, err := APIRequest("/api/releases/packages/create", url.Values{
"applicationName": {"DFHack"},
"releaseNumber": {"0.0.0"},
"$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},
"releaseNumber": {"0.0.0"},
"$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 {
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 develop
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 := APIRequest("/api/releases/create", url.Values{
"applicationName": {"DFHack"},
"releaseNumber": {tag},
"pipelineName": {"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 := APIRequest("/api/releases/create", url.Values{
"applicationName": {plugin},
"releaseNumber": {tag},
"pipelineName": {"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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment