-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
162 lines (143 loc) · 4.31 KB
/
main.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package main
import (
"flag"
"fmt"
"html/template"
"net/http"
"strings"
"sync"
"time"
"github.com/Masterminds/sprig"
"github.com/apex/log"
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
)
func main() {
data, err := Asset("gen/version")
if err != nil {
log.WithError(err).Error("error")
}
version := strings.TrimSpace(string(data))
log.Infof("kubernetes-ingressify version %s", version)
configPath := flag.String("config", "", "path to the config file")
dryRun := flag.Bool("dry-run", false, "Run once without hooks and exits")
flag.Parse()
config := ReadConfig(*configPath)
opsStatus := make(chan *OpsStatus, 10)
defer close(opsStatus)
duration, err := config.getInterval()
if err != nil {
log.WithError(err).Error("Failed to parse interval")
return
}
fmap := template.FuncMap{
"GroupByHost": GroupByHost,
"GroupByPath": GroupByPath,
"GroupBySvcNs": GroupBySvcNs,
"OrderByPathLen": OrderByPathLen,
"AsMap": AsMap,
"AsSlice": AsSlice,
}
clientset, err := GetKubeClient(config.Kubeconfig)
if err != nil {
log.WithError(err).Error("Failed to build k8s client")
return
}
tmpl, err := PrepareTemplate(config.InTemplate, BuildFuncMap(fmap, sprig.FuncMap()))
if err != nil {
log.WithError(err).Error("Failed to prepare template")
return
}
if *dryRun {
err = render(config.OutTemplate, clientset, tmpl)
if err != nil {
log.WithError(err).Error("Failed to render template")
panic(err)
}
} else {
go func() {
for range time.NewTicker(duration).C {
err = render(config.OutTemplate, clientset, tmpl)
if err != nil {
log.WithError(err).Error("Failed to render template")
opsStatus <- &OpsStatus{isSuccess: false, timestamp: time.Now(), error: err}
continue //we don't bother to exec hooks since the rendering failed
}
err = execHooks(config, opsStatus)
if err != nil {
opsStatus <- &OpsStatus{isSuccess: false, timestamp: time.Now(), error: err}
continue
}
opsStatus <- &OpsStatus{isSuccess: true, timestamp: time.Now()}
}
}()
log.WithError(runHealthCheckServer(opsStatus, duration, config.HealthCheckPort)).Error("Health server is down...")
}
}
func runHealthCheckServer(status chan *OpsStatus, duration time.Duration, port uint32) error {
lastReport := OpsStatus{isSuccess: true, timestamp: time.Now()}
healthHandler := healthHandler{opsStatus: status, cacheExpirationTime: duration, lastReport: &lastReport}
http.HandleFunc("/health", healthHandler.ServeHTTP)
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}
type healthHandler struct {
opsStatus chan *OpsStatus
cacheExpirationTime time.Duration
lastReport *OpsStatus
sync.Mutex
}
func (hh *healthHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
hh.Lock()
select {
case currentReport := <-hh.opsStatus:
*hh.lastReport = *currentReport
createHealthResponse(*currentReport, writer)
default:
if time.Now().Sub(hh.lastReport.timestamp) > hh.cacheExpirationTime {
createHealthResponse(OpsStatus{
isSuccess: false, error: errors.New("Seems that k8s-ingressify is stuck")}, writer)
} else {
createHealthResponse(*hh.lastReport, writer)
}
}
hh.Unlock()
}
func createHealthResponse(lastReport OpsStatus, writer http.ResponseWriter) {
if lastReport.isSuccess {
writer.WriteHeader(http.StatusOK)
fmt.Fprint(writer, "Healthy !\n")
} else {
writer.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(writer, "Unhealthy: %s !\n", lastReport.error)
}
}
// OpsStatus holds information to track failures/success of render and execHooks functions
// this information gets bubbled up to the health check.
type OpsStatus struct {
isSuccess bool
error error
timestamp time.Time
}
func execHooks(config Config, renderReport chan<- *OpsStatus) error {
log.Info("Running post hook")
out, err := ExecHook(config.Hooks.PostRender)
if err != nil {
log.WithError(err).Error("Failed to run post hook")
return err
}
log.Info("Output from post hook")
fmt.Println(out)
return nil
}
func render(outPath string, clientset *kubernetes.Clientset, tmpl *template.Template) error {
irules, err := ScrapeIngresses(clientset, "")
if err != nil {
return err
}
cxt := ICxt{IngRules: ToIngressifyRule(irules)}
err = RenderTemplate(tmpl, outPath, cxt)
if err != nil {
return err
}
return nil
}