/
heroku.go
117 lines (105 loc) · 3.73 KB
/
heroku.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// https://devcenter.heroku.com/articles/deploy-hooks#http-post-hook
// The parameters included in the request are the same as the variables available in the hook message: app, user, url, head, head_long, git_log and release. See below for their descriptions.
// This is an example payload:
// curl -X POST http://localhost:8080/$SECRET -d app=secure-woodland-9775 -d user=example%40example.com -d url=http://secure-woodland-9775.herokuapp.com -d head=4f20bdd -d head_long=4f20bdd -d prev_head= -d git_log=%20%20*%20Michael%20Friis%3A%20add%20bar -d release=v7
package main
import (
"encoding/json"
"fmt"
"github.com/garyburd/redigo/redis"
"github.com/gorilla/schema"
"log"
"net/http"
"regexp"
)
var (
decoder = schema.NewDecoder()
stagingRegexp = regexp.MustCompile(`staging`)
productionRegexp = regexp.MustCompile(`production|\bprod\b`)
)
func init() {
decoder.IgnoreUnknownKeys(true)
}
type HerokuAppEnv map[string]string
type HerokuAppState struct {
App string `schema:"app"`
User string `schema:"user"`
Url string `schema:"url"`
Head string `schema:"head"`
HeadLong string `schema:"head_long"`
PrevHead string `schema:"prev_head"`
GitLog string `schema:"git_log"`
Release string `schema:"release"`
UUID string `schema:"app_uuid"`
Environment string
Env HerokuAppEnv
}
// Given a request for Heroku DeployHook, return the current state of the app. This will include all fields from the request, all ENV vars for the app, and the commit hash of the previously deployed commit.
func ParseWebhook(r *http.Request) (state *HerokuAppState, err error) {
state = new(HerokuAppState)
err = decoder.Decode(state, r.PostForm)
if err != nil {
fmt.Printf("Problem parsing webhook:", err)
}
state.SetPrevHead()
state.FetchEnv()
state.GuessEnvironment()
fmt.Printf("%#v\n", state)
return state, err
}
func (state *HerokuAppState) GuessEnvironment() {
if state.Env["RAILS_ENV"] != "" {
state.Environment = state.Env["RAILS_ENV"]
} else if state.Env["RACK_ENV"] != "" {
state.Environment = state.Env["RACK_ENV"]
} else {
switch {
case stagingRegexp.MatchString(state.App):
state.Environment = "staging"
case productionRegexp.MatchString(state.App):
state.Environment = "production"
default:
state.Environment = "development"
}
}
}
// Return a GitHub compare URL if the repository is configured, otherwise just return the plain URL.
func (state *HerokuAppState) URL() (url string) {
if state.Env["GITHUB_REPO"] == "" {
url = state.Url
} else {
url = "https://github.com/" + state.Env["GITHUB_REPO"]
if state.PrevHead != "" {
url = fmt.Sprint(url, "/compare/", state.PrevHead, "...", state.HeadLong)
}
}
return url
}
func (state *HerokuAppState) SetPrevHead() {
if state.PrevHead != "" {
fmt.Printf("Heroku finally started sending PrevHead!\n")
return
}
conn := RedisPool.Get()
defer conn.Close()
key := fmt.Sprintf("%s:%s", state.UUID, "commit")
state.PrevHead, _ = redis.String(conn.Do("GET", key))
conn.Do("Set", key, state.Head)
}
func (state *HerokuAppState) FetchEnv() {
state.Env = MustGetHerokuAppEnv(state.UUID, config.HerokuAuthToken)
}
// https://devcenter.heroku.com/articles/platform-api-reference#config-vars
func MustGetHerokuAppEnv(appNameOrUuid string, authToken string) (appEnv HerokuAppEnv) {
req, err := http.NewRequest("GET", "https://api.heroku.com/apps/"+appNameOrUuid+"/config-vars", nil)
req.Header.Add("Accept", "application/vnd.heroku+json; version=3")
req.Header.Add("Authorization", "Bearer "+authToken)
req.Header.Add("User-Agent", "Bownse: The Heroku Webhook Multiplexer")
resp, err := client.Do(req)
defer resp.Body.Close()
if err != nil {
log.Panic(err)
}
json.NewDecoder(resp.Body).Decode(&appEnv)
return appEnv
}