-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
127 lines (101 loc) · 2.9 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
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
"github.com/pkg/errors"
)
type Tailnet struct {
Devices []Device `json:"devices"`
}
type Device struct {
Addresses []string `json:"addresses"`
Authorized bool `json:"authorized"`
BlocksIncomingConnections bool
ClientVersion string
Expires time.Time
Hostname string
Name string
ID string
External bool `json:"isExternal"`
KeyExpiryDisabled bool
LastSeen time.Time
OS string
UpdateAvailable bool
User string
// This can be empty in responses and causes issues for JSON parsing:
//Created *time.Time `json:"created,omitEmpty"`
}
const (
DeviceURL = "https://api.tailscale.com/api/v2/tailnet/%s/devices"
)
var (
addr = flag.String("listen-address", ":8080", "The address to listen on for HTTP requests.")
tailnet = flag.String("tailnet", "", "The Tailscale network to query.")
token = flag.String("token", "", "The Tailscale API token.")
interval = flag.Duration("interval", 5*time.Minute, "The interval to poll the Tailscale API.")
debug = flag.Bool("debug", false, "Debug output")
)
func debugf(fmt string, args ...interface{}) {
if *debug {
log.Printf(fmt, args...)
}
}
func fetchDevices(metrics *metrics, tailnet, key string) error {
client := &http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest("GET", fmt.Sprintf(DeviceURL, tailnet), nil)
if err != nil {
return errors.Wrap(err, "creating request")
}
req.SetBasicAuth(key, "")
response, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "executing request")
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return errors.Errorf("Tailscale API request returned unexpected status code: %d - %s", response.StatusCode, response.Status)
}
buf, err := ioutil.ReadAll(response.Body)
if err != nil {
return errors.Wrap(err, "reading response body")
}
tnet := &Tailnet{}
if err := json.Unmarshal(buf, tnet); err != nil {
return errors.Wrap(err, "parsing JSON")
}
for _, device := range tnet.Devices {
metrics.generateMetrics(device)
debugf("%+v\n", device)
}
return nil
}
func main() {
flag.Parse()
if *tailnet == "" || *token == "" {
flag.Usage()
os.Exit(1)
}
metrics := &metrics{}
if err := fetchDevices(metrics, *tailnet, *token); err != nil {
log.Printf("Error fetching devices: %+v", err)
}
go func() {
c := time.Tick(*interval)
for range c {
if err := fetchDevices(metrics, *tailnet, *token); err != nil {
log.Printf("Error fetching devices: %+v", err)
}
}
}()
http.Handle("/metrics", metrics.metricsHandler())
log.Printf("Listening on: %s\n", *addr)
log.Fatal(http.ListenAndServe(*addr, nil))
}