-
Notifications
You must be signed in to change notification settings - Fork 12
/
filehistory.go
194 lines (176 loc) · 5.02 KB
/
filehistory.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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Package filehistory is a simple file-backed implementation for bot plugin
// and job histories.
package filehistory
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"strings"
"sync"
"github.com/lnxjedi/gopherbot/robot"
)
var historyPath string
var handler robot.Handler
// TODO: move to bot.historyStdFlags
const logFlags = log.LstdFlags
type historyConfig struct {
Directory string `yaml:"Directory"` // path to histories
URLPrefix string `yaml:"URLPrefix"` // Optional URL prefix corresponding to the Directory
// If LocalPort set, passed to http.ListenAndServe to serve static files
LocalPort string `yaml:"LocalPort"`
}
type histref struct {
name string
idx int
}
var current = struct {
running map[histref]bool
sync.Mutex
}{
make(map[histref]bool),
sync.Mutex{},
}
type historyFile struct {
l *log.Logger
f *os.File
name string
idx int
path string
keep bool
}
// Log takes a line of text and stores it in the history file
func (hf *historyFile) Log(line string) {
hf.l.Println(line)
}
// Section creates a new named section in the history file, for separating
// output from jobs/plugins in a pipeline
func (hf *historyFile) Line(line string) {
hf.l.SetFlags(0)
hf.l.Println(line)
hf.l.SetFlags(logFlags)
}
// Close sets the logger output to discard and closes the log file
func (hf *historyFile) Close() {
hf.l.SetOutput(ioutil.Discard)
hf.f.Close()
}
// Finalize removes the log if needed
func (hf *historyFile) Finalize() {
hr := histref{hf.name, hf.idx}
current.Lock()
delete(current.running, hr)
current.Unlock()
if hf.keep {
return
}
if rerr := os.Remove(hf.path); rerr != nil {
handler.Log(robot.Error, "Removing %s: %v", hf.path, rerr)
}
}
var fhc historyConfig
// NewLog initializes and returns a historyFile, as well as cleaning up old
// logs.
func (fhc *historyConfig) NewLog(tag string, index, maxHistories int) (robot.HistoryLogger, error) {
tag = strings.Replace(tag, `\`, ":", -1)
tag = strings.Replace(tag, `/`, ":", -1)
dirPath := path.Join(fhc.Directory, tag)
filePath := path.Join(dirPath, fmt.Sprintf("run-%d.log", index))
handler.RaisePriv("creating new log for: " + tag)
if err := os.MkdirAll(dirPath, 0755); err != nil {
return nil, fmt.Errorf("Error creating history directory '%s': %v", dirPath, err)
}
file, err := os.Create(filePath)
if err != nil {
return nil, fmt.Errorf("Error creating history file '%s': %v", filePath, err)
}
keep := maxHistories != 0
hl := log.New(file, "", logFlags)
hr := histref{tag, index}
current.Lock()
current.running[hr] = keep
current.Unlock()
hf := &historyFile{
hl,
file,
tag,
index,
filePath,
keep,
}
if maxHistories > 0 {
for i := index - maxHistories; i >= 0; i-- {
rmPath := path.Join(dirPath, fmt.Sprintf("run-%d.log", i))
_, err := os.Stat(rmPath)
if err != nil {
break
}
rerr := os.Remove(rmPath)
if rerr != nil {
handler.Log(robot.Error, "Error removing old log file '%s': %v", rmPath, rerr)
// assume it's pointless to keep trying to delete files
break
}
}
}
return hf, nil
}
// GetLog returns an io.Reader
func (fhc *historyConfig) GetLog(tag string, index int) (io.Reader, error) {
tag = strings.Replace(tag, `\`, ":", -1)
tag = strings.Replace(tag, `/`, ":", -1)
dirPath := path.Join(fhc.Directory, tag)
filePath := path.Join(dirPath, fmt.Sprintf("run-%d.log", index))
return os.Open(filePath)
}
// GetLogURL returns the permanent link to the history
func (fhc *historyConfig) GetLogURL(tag string, index int) (string, bool) {
if len(fhc.URLPrefix) == 0 {
return "", false
}
hr := histref{tag, index}
current.Lock()
keep, ok := current.running[hr]
current.Unlock()
if ok && !keep {
return "", false
}
tag = strings.Replace(tag, `\`, ":", -1)
tag = strings.Replace(tag, `/`, ":", -1)
prefix := strings.TrimRight(fhc.URLPrefix, "/")
htmlPath := fmt.Sprintf("%s/%s/run-%d.log", prefix, tag, index)
return htmlPath, true
}
// MakeLogURL publishes a history to a URL and returns the URL
func (fhc *historyConfig) MakeLogURL(tag string, index int) (string, bool) {
return "", false
}
func provider(r robot.Handler) robot.HistoryProvider {
handler = r
handler.GetHistoryConfig(&fhc)
if len(fhc.Directory) == 0 {
handler.Log(robot.Error, "HistoryConfig missing value for Directory required by 'file' history provider")
return nil
}
historyPath = fhc.Directory
handler.RaisePriv("initializing file history")
if err := r.GetDirectory(historyPath); err != nil {
handler.Log(robot.Error, "Checking history directory '%s': %v", historyPath, err)
return nil
}
if len(fhc.LocalPort) > 0 {
go func() {
handler.Log(robot.Info, "Starting fileserver listener for file history provider")
log.Fatal(http.ListenAndServe(fhc.LocalPort, http.FileServer(http.Dir(historyPath))))
}()
}
if len(fhc.LocalPort) > 0 {
handler.Log(robot.Info, "Initialized file history provider with directory: '%s'; serving on: '%s'", historyPath, fhc.LocalPort)
} else {
handler.Log(robot.Info, "Initialized file history provider with directory: '%s'", historyPath)
}
return &fhc
}