-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.go
145 lines (131 loc) · 3.82 KB
/
app.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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package main
import (
"crypto/subtle"
"fmt"
"log"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
)
type (
App struct {
Config *Config
Clientset *kubernetes.Clientset
Counter *prometheus.CounterVec
}
AppResponseWriter struct {
http.ResponseWriter
StatusCode int
LogMessage string
}
)
// NewApp create a new updater app
func NewApp(config *Config, clientset *kubernetes.Clientset) *App {
// create counter and initialize
counter := promauto.NewCounterVec(prometheus.CounterOpts{
Name: "bitsbeats_updater_deployment",
Help: "increased whenever a update is triggered",
}, []string{"action"})
counter.With(prometheus.Labels{"action": "updated"}).Add(0)
counter.With(prometheus.Labels{"action": "skipped"}).Add(0)
return &App{
Config: config,
Clientset: clientset,
Counter: counter,
}
}
// Handle provide a http.HandleFunc to validate requests and trigger the patch
func (a *App) Handle(w http.ResponseWriter, r *http.Request) {
// read http headers
var token string
if tokenList, ok := r.Header["Token"]; ok {
token = tokenList[0]
} else {
w.(*AppResponseWriter).Abort(http.StatusForbidden, "token header missing")
return
}
// verify token
if subtle.ConstantTimeCompare([]byte(token), a.Config.Token) != 1 {
w.(*AppResponseWriter).Abort(http.StatusForbidden, "invalid token")
return
}
// patch deployment
t := time.Now().Format(time.RFC3339)
updating, err := a.Patch(t)
if err != nil {
msg := fmt.Sprintf("unable to update deployment: %s", err)
w.(*AppResponseWriter).Abort(http.StatusInternalServerError, msg)
return
}
if updating {
msg := fmt.Sprintf("updated deployment: rolling out new version %s", t)
w.(*AppResponseWriter).Ok(msg)
a.Counter.With(prometheus.Labels{"action": "updated"}).Inc()
} else {
w.(*AppResponseWriter).Ok("updated deployment: no change detected")
a.Counter.With(prometheus.Labels{"action": "skipped"}).Inc()
}
}
// Middleware is a http middleware with extended logging
func (a *App) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
w = &AppResponseWriter{w, 200, ""}
next.ServeHTTP(w, r)
remote := r.RemoteAddr
if forwarded, ok := r.Header["X-Forwarded-For"]; ok {
remote = forwarded[0]
}
msg := fmt.Sprintf(
"%d %s %fs %s %s",
w.(*AppResponseWriter).StatusCode, remote,
time.Since(t).Seconds(), r.Method, r.URL,
)
if w.(*AppResponseWriter).LogMessage != "" {
msg += fmt.Sprintf(" - %q", w.(*AppResponseWriter).LogMessage)
}
log.Print(msg)
})
}
// Patch patches a Deployment with an updater annotation to tigger a deployment
func (a *App) Patch(annotationValue string) (updating bool, err error) {
template := `
{
"spec": {
"template": {
"metadata": {
"annotations": {
"thobits.com/updater": %q
}
}
}
}
}`
resp, err := a.Clientset.AppsV1().Deployments(a.Config.Namespace).Patch(
a.Config.Deployment,
types.StrategicMergePatchType,
[]byte(fmt.Sprintf(template, annotationValue)))
updating = false
if resp.Status.ObservedGeneration != resp.ObjectMeta.Generation {
updating = true
}
return
}
// WriteHeader wraps ResponseWriters WriteHeader
func (w *AppResponseWriter) WriteHeader(statusCode int) {
w.ResponseWriter.WriteHeader(statusCode)
w.StatusCode = statusCode
}
// Abort defines the http errorcode an the message for the logger
func (w *AppResponseWriter) Abort(statusCode int, message string) {
w.WriteHeader(statusCode)
w.StatusCode = statusCode
w.LogMessage = message
}
// Ok defines the message for the logger
func (w *AppResponseWriter) Ok(message string) {
w.LogMessage = message
}