Skip to content

Commit 448f4b4

Browse files
authored
Merge pull request #331 from nan-yu/release-3.x
Export the error details to an error file
2 parents 50917bf + 8ea4492 commit 448f4b4

File tree

2 files changed

+184
-64
lines changed

2 files changed

+184
-64
lines changed

cmd/git-sync/main.go

Lines changed: 137 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package main // import "k8s.io/git-sync/cmd/git-sync"
2121
import (
2222
"bytes"
2323
"context"
24+
"encoding/json"
2425
"flag"
2526
"fmt"
2627
"io/ioutil"
@@ -37,6 +38,7 @@ import (
3738
"time"
3839

3940
"github.com/go-logr/glogr"
41+
"github.com/go-logr/logr"
4042
"github.com/prometheus/client_golang/prometheus"
4143
"github.com/prometheus/client_golang/prometheus/promhttp"
4244
"k8s.io/git-sync/pkg/pid1"
@@ -60,6 +62,8 @@ var flRoot = flag.String("root", envString("GIT_SYNC_ROOT", envString("HOME", ""
6062
"the root directory for git-sync operations, under which --dest will be created")
6163
var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""),
6264
"the name of (a symlink to) a directory in which to check-out files under --root (defaults to the leaf dir of --repo)")
65+
var flErrorFile = flag.String("error-file", envString("GIT_SYNC_ERROR_FILE", ""),
66+
"the name of a file into which errors will be written under --root (defaults to \"\", disabling error reporting)")
6367
var flWait = flag.Float64("wait", envFloat("GIT_SYNC_WAIT", 1),
6468
"the number of seconds between syncs")
6569
var flSyncTimeout = flag.Int("timeout", envInt("GIT_SYNC_TIMEOUT", 120),
@@ -119,7 +123,7 @@ var flHTTPMetrics = flag.Bool("http-metrics", envBool("GIT_SYNC_HTTP_METRICS", t
119123
var flHTTPprof = flag.Bool("http-pprof", envBool("GIT_SYNC_HTTP_PPROF", false),
120124
"enable the pprof debug endpoints on git-sync's HTTP endpoint")
121125

122-
var log = glogr.New()
126+
var log *customLogger
123127

124128
// Total pull/error, summary on pull duration
125129
var (
@@ -155,6 +159,90 @@ const (
155159
submodulesOff = "off"
156160
)
157161

162+
type customLogger struct {
163+
logr.Logger
164+
errorFile string
165+
}
166+
167+
func (l customLogger) Error(err error, msg string, kvList ...interface{}) {
168+
l.Logger.Error(err, msg, kvList...)
169+
if l.errorFile == "" {
170+
return
171+
}
172+
payload := struct {
173+
Msg string
174+
Err string
175+
Args map[string]interface{}
176+
}{
177+
Msg: msg,
178+
Err: err.Error(),
179+
Args: map[string]interface{}{},
180+
}
181+
if len(kvList)%2 != 0 {
182+
kvList = append(kvList, "<no-value>")
183+
}
184+
for i := 0; i < len(kvList); i += 2 {
185+
k, ok := kvList[i].(string)
186+
if !ok {
187+
k = fmt.Sprintf("%v", kvList[i])
188+
}
189+
payload.Args[k] = kvList[i+1]
190+
}
191+
jb, err := json.Marshal(payload)
192+
if err != nil {
193+
l.Logger.Error(err, "can't encode error payload")
194+
content := fmt.Sprintf("%v", err)
195+
l.writeContent([]byte(content))
196+
} else {
197+
l.writeContent(jb)
198+
}
199+
}
200+
201+
// exportError exports the error to the error file if --export-error is enabled.
202+
func (l *customLogger) exportError(content string) {
203+
if l.errorFile == "" {
204+
return
205+
}
206+
l.writeContent([]byte(content))
207+
}
208+
209+
// writeContent writes the error content to the error file.
210+
func (l *customLogger) writeContent(content []byte) {
211+
tmpFile, err := ioutil.TempFile(*flRoot, "tmp-err-")
212+
if err != nil {
213+
l.Logger.Error(err, "can't create temporary error-file", "directory", *flRoot, "prefix", "tmp-err-")
214+
return
215+
}
216+
defer func() {
217+
if err := tmpFile.Close(); err != nil {
218+
l.Logger.Error(err, "can't close temporary error-file", "filename", tmpFile.Name())
219+
}
220+
}()
221+
222+
if _, err = tmpFile.Write(content); err != nil {
223+
l.Logger.Error(err, "can't write to temporary error-file", "filename", tmpFile.Name())
224+
return
225+
}
226+
227+
if err := os.Rename(tmpFile.Name(), l.errorFile); err != nil {
228+
l.Logger.Error(err, "can't rename to error-file", "temp-file", tmpFile.Name(), "error-file", l.errorFile)
229+
return
230+
}
231+
}
232+
233+
// deleteErrorFile deletes the error file.
234+
func (l *customLogger) deleteErrorFile() {
235+
if l.errorFile == "" {
236+
return
237+
}
238+
if err := os.Remove(l.errorFile); err != nil {
239+
if os.IsNotExist(err) {
240+
return
241+
}
242+
l.Logger.Error(err, "can't delete the error-file", "filename", l.errorFile)
243+
}
244+
}
245+
158246
func init() {
159247
prometheus.MustRegister(syncDuration)
160248
prometheus.MustRegister(syncCount)
@@ -184,7 +272,7 @@ func envInt(key string, def int) int {
184272
if env := os.Getenv(key); env != "" {
185273
val, err := strconv.ParseInt(env, 0, 0)
186274
if err != nil {
187-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
275+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%d\n", err, key, env, def)
188276
return def
189277
}
190278
return int(val)
@@ -196,7 +284,7 @@ func envFloat(key string, def float64) float64 {
196284
if env := os.Getenv(key); env != "" {
197285
val, err := strconv.ParseFloat(env, 64)
198286
if err != nil {
199-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
287+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%f\n", err, key, env, def)
200288
return def
201289
}
202290
return val
@@ -208,7 +296,7 @@ func envDuration(key string, def time.Duration) time.Duration {
208296
if env := os.Getenv(key); env != "" {
209297
val, err := time.ParseDuration(env)
210298
if err != nil {
211-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
299+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%d\n", err, key, env, def)
212300
return def
213301
}
214302
return val
@@ -220,8 +308,7 @@ func setFlagDefaults() {
220308
// Force logging to stderr (from glog).
221309
stderrFlag := flag.Lookup("logtostderr")
222310
if stderrFlag == nil {
223-
fmt.Fprintf(os.Stderr, "ERROR: can't find flag 'logtostderr'\n")
224-
os.Exit(1)
311+
handleError(false, "ERROR: can't find flag 'logtostderr'")
225312
}
226313
stderrFlag.Value.Set("true")
227314
}
@@ -241,35 +328,33 @@ func main() {
241328
setFlagDefaults()
242329
flag.Parse()
243330

331+
var errorFile string
332+
if *flErrorFile != "" {
333+
errorFile = filepath.Join(*flRoot, *flErrorFile)
334+
}
335+
log = &customLogger{glogr.New(), errorFile}
336+
244337
if *flVer {
245338
fmt.Println(version.VERSION)
246339
os.Exit(0)
247340
}
248341

249342
if *flRepo == "" {
250-
fmt.Fprintf(os.Stderr, "ERROR: --repo must be specified\n")
251-
flag.Usage()
252-
os.Exit(1)
343+
handleError(true, "ERROR: --repo must be specified")
253344
}
254345

255346
if *flDepth < 0 { // 0 means "no limit"
256-
fmt.Fprintf(os.Stderr, "ERROR: --depth must be greater than or equal to 0\n")
257-
flag.Usage()
258-
os.Exit(1)
347+
handleError(true, "ERROR: --depth must be greater than or equal to 0")
259348
}
260349

261350
switch *flSubmodules {
262351
case submodulesRecursive, submodulesShallow, submodulesOff:
263352
default:
264-
fmt.Fprintf(os.Stderr, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff)
265-
flag.Usage()
266-
os.Exit(1)
353+
handleError(true, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff)
267354
}
268355

269356
if *flRoot == "" {
270-
fmt.Fprintf(os.Stderr, "ERROR: --root must be specified\n")
271-
flag.Usage()
272-
os.Exit(1)
357+
handleError(true, "ERROR: --root must be specified")
273358
}
274359

275360
if *flDest == "" {
@@ -278,81 +363,59 @@ func main() {
278363
}
279364

280365
if strings.Contains(*flDest, "/") {
281-
fmt.Fprintf(os.Stderr, "ERROR: --dest must be a leaf name, not a path\n")
282-
flag.Usage()
283-
os.Exit(1)
366+
handleError(true, "ERROR: --dest must be a leaf name, not a path")
284367
}
285368

286369
if *flWait < 0 {
287-
fmt.Fprintf(os.Stderr, "ERROR: --wait must be greater than or equal to 0\n")
288-
flag.Usage()
289-
os.Exit(1)
370+
handleError(true, "ERROR: --wait must be greater than or equal to 0")
290371
}
291372

292373
if *flSyncTimeout < 0 {
293-
fmt.Fprintf(os.Stderr, "ERROR: --timeout must be greater than 0\n")
294-
flag.Usage()
295-
os.Exit(1)
374+
handleError(true, "ERROR: --timeout must be greater than 0")
296375
}
297376

298377
if *flWebhookURL != "" {
299378
if *flWebhookStatusSuccess < -1 {
300-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-success-status must be a valid HTTP code or -1\n")
301-
flag.Usage()
302-
os.Exit(1)
379+
handleError(true, "ERROR: --webhook-success-status must be a valid HTTP code or -1")
303380
}
304381
if *flWebhookTimeout < time.Second {
305-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-timeout must be at least 1s\n")
306-
flag.Usage()
307-
os.Exit(1)
382+
handleError(true, "ERROR: --webhook-timeout must be at least 1s")
308383
}
309384
if *flWebhookBackoff < time.Second {
310-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-backoff must be at least 1s\n")
311-
flag.Usage()
312-
os.Exit(1)
385+
handleError(true, "ERROR: --webhook-backoff must be at least 1s")
313386
}
314387
}
315388

316389
if _, err := exec.LookPath(*flGitCmd); err != nil {
317-
fmt.Fprintf(os.Stderr, "ERROR: git executable %q not found: %v\n", *flGitCmd, err)
318-
os.Exit(1)
390+
handleError(false, "ERROR: git executable %q not found: %v", *flGitCmd, err)
319391
}
320392

321393
if *flSSH {
322394
if *flUsername != "" {
323-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --username may be specified\n")
324-
os.Exit(1)
395+
handleError(false, "ERROR: only one of --ssh and --username may be specified")
325396
}
326397
if *flPassword != "" {
327-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --password may be specified\n")
328-
os.Exit(1)
398+
handleError(false, "ERROR: only one of --ssh and --password may be specified")
329399
}
330400
if *flAskPassURL != "" {
331-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --askpass-url may be specified\n")
332-
os.Exit(1)
401+
handleError(false, "ERROR: only one of --ssh and --askpass-url may be specified")
333402
}
334403
if *flCookieFile {
335-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --cookie-file may be specified\n")
336-
os.Exit(1)
404+
handleError(false, "ERROR: only one of --ssh and --cookie-file may be specified")
337405
}
338406
if *flSSHKeyFile == "" {
339-
fmt.Fprintf(os.Stderr, "ERROR: --ssh-key-file must be specified when --ssh is specified\n")
340-
flag.Usage()
341-
os.Exit(1)
407+
handleError(true, "ERROR: --ssh-key-file must be specified when --ssh is specified")
342408
}
343409
if *flSSHKnownHosts {
344410
if *flSSHKnownHostsFile == "" {
345-
fmt.Fprintf(os.Stderr, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified\n")
346-
flag.Usage()
347-
os.Exit(1)
411+
handleError(true, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified")
348412
}
349413
}
350414
}
351415

352416
if *flAddUser {
353417
if err := addUser(); err != nil {
354-
fmt.Fprintf(os.Stderr, "ERROR: can't write to /etc/passwd: %v\n", err)
355-
os.Exit(1)
418+
handleError(false, "ERROR: can't write to /etc/passwd: %v", err)
356419
}
357420
}
358421

@@ -362,30 +425,26 @@ func main() {
362425

363426
if *flUsername != "" && *flPassword != "" {
364427
if err := setupGitAuth(ctx, *flUsername, *flPassword, *flRepo); err != nil {
365-
fmt.Fprintf(os.Stderr, "ERROR: can't create .netrc file: %v\n", err)
366-
os.Exit(1)
428+
handleError(false, "ERROR: can't create .netrc file: %v", err)
367429
}
368430
}
369431

370432
if *flSSH {
371433
if err := setupGitSSH(*flSSHKnownHosts); err != nil {
372-
fmt.Fprintf(os.Stderr, "ERROR: can't configure SSH: %v\n", err)
373-
os.Exit(1)
434+
handleError(false, "ERROR: can't configure SSH: %v", err)
374435
}
375436
}
376437

377438
if *flCookieFile {
378439
if err := setupGitCookieFile(ctx); err != nil {
379-
fmt.Fprintf(os.Stderr, "ERROR: can't set git cookie file: %v\n", err)
380-
os.Exit(1)
440+
handleError(false, "ERROR: can't set git cookie file: %v", err)
381441
}
382442
}
383443

384444
if *flAskPassURL != "" {
385445
if err := callGitAskPassURL(ctx, *flAskPassURL); err != nil {
386446
askpassCount.WithLabelValues(metricKeyError).Inc()
387-
fmt.Fprintf(os.Stderr, "ERROR: failed to call ASKPASS callback URL: %v\n", err)
388-
os.Exit(1)
447+
handleError(false, "ERROR: failed to call ASKPASS callback URL: %v", err)
389448
}
390449
askpassCount.WithLabelValues(metricKeySuccess).Inc()
391450
}
@@ -404,8 +463,7 @@ func main() {
404463
if *flHTTPBind != "" {
405464
ln, err := net.Listen("tcp", *flHTTPBind)
406465
if err != nil {
407-
fmt.Fprintf(os.Stderr, "ERROR: unable to bind HTTP endpoint: %v\n", err)
408-
os.Exit(1)
466+
handleError(false, "ERROR: unable to bind HTTP endpoint: %v", err)
409467
}
410468
mux := http.NewServeMux()
411469
go func() {
@@ -480,19 +538,22 @@ func main() {
480538

481539
if initialSync {
482540
if *flOneTime {
541+
log.deleteErrorFile()
483542
os.Exit(0)
484543
}
485544
if isHash, err := revIsHash(ctx, *flRev, *flRoot); err != nil {
486545
log.Error(err, "can't tell if rev is a git hash, exiting", "rev", *flRev)
487546
os.Exit(1)
488547
} else if isHash {
489548
log.V(0).Info("rev appears to be a git hash, no further sync needed", "rev", *flRev)
549+
log.deleteErrorFile()
490550
sleepForever()
491551
}
492552
initialSync = false
493553
}
494554

495555
failCount = 0
556+
log.deleteErrorFile()
496557
log.V(1).Info("next sync", "wait_time", waitTime(*flWait))
497558
cancel()
498559
time.Sleep(waitTime(*flWait))
@@ -517,6 +578,18 @@ func sleepForever() {
517578
os.Exit(0)
518579
}
519580

581+
// handleError prints the error to the standard error, prints the usage if the `printUsage` flag is true,
582+
// exports the error to the error file and exits the process with the exit code.
583+
func handleError(printUsage bool, format string, a ...interface{}) {
584+
s := fmt.Sprintf(format, a...)
585+
fmt.Fprintln(os.Stderr, s)
586+
if printUsage {
587+
flag.Usage()
588+
}
589+
log.exportError(s)
590+
os.Exit(1)
591+
}
592+
520593
// Put the current UID/GID into /etc/passwd so SSH can look it up. This
521594
// assumes that we have the permissions to write to it.
522595
func addUser() error {

0 commit comments

Comments
 (0)