-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathprom.go
115 lines (102 loc) · 3.05 KB
/
prom.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
// This file is part of netbackup, a frontend to simplify periodic backups.
// For further information, check https://github.com/marcopaganini/netbackup
//
// (C) 2015-2024 by Marco Paganini <paganini AT paganini DOT net>
package main
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"syscall"
"time"
)
// exists returns true if the file exists, false otherwise.
func exists(fname string) bool {
if _, err := os.Stat(fname); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
// writeNodeTextFile writes a record in a prometheus node-exporter
// compatible "textfile" format. The record is formatted as:
//
// backup{name="foobar", job="netbackup", status="success"} <timestamp>
//
// Existing lines with the same format and name will be overwritten.
// All other lines will remain intact.
//
// The function employs FLock() on a separate lockfile to prevent race
// conditions when modifying to the original file. All writes go into a
// temporary file that is atomically renamed to the final name once work is
// done.
func writeNodeTextFile(textfile string, name string) error {
dirname, fname := filepath.Split(textfile)
// Create a textfile under /tmp and Flock it.
lockfile := filepath.Join("/tmp", fname+".lock")
lock, err := os.OpenFile(lockfile, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return fmt.Errorf("error opening lockfile: %v", err)
}
defer lock.Close()
if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
return err
}
defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)
data := []byte{}
if exists(textfile) {
data, err = os.ReadFile(textfile)
if err != nil {
return fmt.Errorf("error reading textfile: %v", err)
}
}
// Rebuild output without any previous lines with the same name
// and the new line added with the current unix timestamp.
matchname, err := regexp.Compile(`backup[\s]*{.*name="` + name + `".*`)
if err != nil {
return err
}
output := []byte{}
for _, line := range bytes.Split(data, []byte("\n")) {
// See https://github.com/golang/go/issues/35130
// To understand why this BS is needed here.
if len(line) == 0 {
continue
}
// Don't copy our own lines.
if matchname.Match(line) {
continue
}
output = append(output, line...)
output = append(output, byte('\n'))
}
// Add our line.
now := time.Now().Unix()
s := fmt.Sprintf("backup{name=%q, job=\"netbackup\", status=\"success\"} %d\n", name, now)
output = append(output, []byte(s)...)
// Write to temporary file and rename it to the original file name.
tempdir := dirname
if dirname == "" {
tempdir = "./"
}
temp, err := os.CreateTemp(tempdir, fname)
if err != nil {
return fmt.Errorf("error creating temp file: %v", err)
}
defer os.Remove(temp.Name())
defer temp.Close()
if err := os.Chmod(temp.Name(), 0664); err != nil {
return err
}
_, err = temp.Write(output)
if err != nil {
return fmt.Errorf("error writing to temp file: %v", err)
}
temp.Close()
if err := os.Rename(temp.Name(), textfile); err != nil {
return fmt.Errorf("error renaming temp file: %v", err)
}
return nil
}