This repository has been archived by the owner on Dec 25, 2020. It is now read-only.
/
main.go
148 lines (129 loc) · 3.33 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
package main
import (
"bytes"
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/Masterminds/semver"
"github.com/VictoriaMetrics/metrics"
"github.com/afoninsky/version-exporter/probers"
"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)
}
for name, probe := range cfg {
log.Printf(`Testing %s with interval %s`, name, probe.Interval)
p, err := probers.New(probe.Type, probe.Config)
if err != nil {
log.Fatal(err)
}
go startProber(name, p, probe.Interval, bucket)
}
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 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 == "" || v == newV {
continue
}
// TODO: parse string to semver
pNewV, err := semver.NewVersion(newV)
if err != nil {
log.Printf("[ERROR] parsing version: %s: %v", name, err)
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,
pNewV,
pNewV.Major(),
pNewV.Minor(),
pNewV.Patch(),
)
metrics.GetOrCreateCounter(labels).Set(1)
}
}
// stores value in storage
func saveVersion(storage *blob.Bucket, name string, v string) error {
w, err := storage.NewWriter(context.Background(), name, nil)
if err != nil {
return err
}
_, err = fmt.Fprint(w, v)
if err != nil {
return err
}
return w.Close()
}
// reads value from storage
func getCurrentVersion(storage *blob.Bucket, name string) (string, 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.String(), nil
}
return "", err
}
defer r.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(r)
return buf.String(), nil
}