This repository has been archived by the owner on Dec 25, 2020. It is now read-only.
/
main.go
153 lines (135 loc) · 3.51 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
package main
import (
"bytes"
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/Masterminds/semver"
"github.com/VictoriaMetrics/metrics"
"github.com/afoninsky/semver-exporter/probers"
"github.com/afoninsky/semver-exporter/probers/helm"
"gocloud.dev/blob"
_ "gocloud.dev/blob/azureblob"
_ "gocloud.dev/blob/fileblob"
_ "gocloud.dev/blob/gcsblob"
_ "gocloud.dev/blob/memblob"
_ "gocloud.dev/blob/s3blob"
"gocloud.dev/gcerrors"
"gopkg.in/yaml.v2"
)
var fListen = flag.String("listen", "127.0.0.1:8080", "The address to listen on for HTTP requests")
var fConfig = flag.String("config", "./probes.yaml", "Configuration file path")
var fEndpoint = flag.String("endpoint", "/metrics", "Metrics HTTP endpoint")
var fStorage = flag.String("storage", "mem://", "Storage path")
type probes map[string]struct {
Interval time.Duration `yaml:"interval"`
Type string `yaml:"type"`
Config string `yaml:"config"`
}
func main() {
flag.Parse()
cfg, err := loadConfig(*fConfig)
if err != nil {
log.Fatal(err)
}
bucket, err := blob.OpenBucket(context.Background(), *fStorage)
if err != nil {
log.Fatal(err)
}
if err := createProbers(bucket, cfg); err != nil {
log.Fatal(err)
}
http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
metrics.WritePrometheus(w, false)
})
log.Printf("Expose metrics: %s%s", *fListen, *fEndpoint)
log.Fatal(http.ListenAndServe(*fListen, nil))
}
func loadConfig(cfgPath string) (probes, error) {
f, err := os.Open(cfgPath)
if err != nil {
return nil, err
}
defer f.Close()
var cfg probes
if err := yaml.NewDecoder(f).Decode(&cfg); err != nil {
return nil, err
}
return cfg, nil
}
func createProbers(storage *blob.Bucket, cfg probes) error {
for name, probe := range cfg {
log.Printf(`Testing %s with interval %s`, name, probe.Interval)
switch probe.Type {
case "helm":
p, err := helm.New(name, probe.Config)
if err != nil {
return err
}
go startProber(name, p, probe.Interval, storage)
default:
return fmt.Errorf(`type %s is not supported`, name)
}
}
return nil
}
func startProber(name string, p probers.Prober, d time.Duration, storage *blob.Bucket) {
for {
time.Sleep(d)
v, err := getCurrentVersion(storage, name)
if err != nil {
log.Fatal(err)
}
newV, err := p.Probe(v)
if err != nil {
log.Printf("[ERROR] %s: %v", name, err)
continue
}
if v == nil || v.String() == newV.String() {
continue
}
if err := saveVersion(storage, name, newV); err != nil {
log.Fatal(err)
}
log.Printf("%s, new version: %s -> %s\n", name, v, newV)
labels := fmt.Sprintf(`semver_release{probe="%s",version="%s",version_major="%d",version_minor="%d",version_patch="%d"}`,
name,
newV,
newV.Major(),
newV.Minor(),
newV.Patch(),
)
metrics.GetOrCreateCounter(labels).Set(1)
}
}
// stores value in storage
func saveVersion(storage *blob.Bucket, name string, v *semver.Version) error {
w, err := storage.NewWriter(context.Background(), name, nil)
if err != nil {
return err
}
_, err = fmt.Fprint(w, v.String())
if err != nil {
return err
}
return w.Close()
}
// reads value from storage
func getCurrentVersion(storage *blob.Bucket, name string) (*semver.Version, error) {
r, err := storage.NewReader(context.Background(), name, nil)
if err != nil {
if gcerrors.Code(err) == gcerrors.NotFound {
v, _ := semver.NewVersion("0.0.0")
return v, nil
}
return nil, err
}
defer r.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(r)
return semver.NewVersion(buf.String())
}