-
-
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 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"] |
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" | |
"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