Skip to content

Commit 7949836

Browse files
authored
Merge pull request #375 from nan-yu/master
Export the error details to an error file
2 parents 6ac6e4e + 53f3fa1 commit 7949836

File tree

3 files changed

+186
-54
lines changed

3 files changed

+186
-54
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ docker run -d \
9191
| GIT_SYNC_SUBMODULES | `--submodules` | git submodule behavior: one of 'recursive', 'shallow', or 'off' | recursive |
9292
| GIT_SYNC_ROOT | `--root` | the root directory for git-sync operations, under which --link will be created | "" |
9393
| GIT_SYNC_LINK | `--link` | the name of a symlink, under --root, which points to a directory in which --repo is checked out (defaults to the leaf dir of --repo) | "" |
94+
| GIT_SYNC_ERROR_FILE | `--error-file` | the name of a file, under --root, into which errors will be written (defaults to "", disabling error reporting) | "" |
9495
| GIT_SYNC_PERIOD | `--period` | how long to wait between syncs, must be >= 10ms | "10s" |
9596
| GIT_SYNC_SYNC_TIMEOUT | `--sync-timeout` | the total time allowed for one complete sync, must be >= 10ms | "120s" |
9697
| GIT_SYNC_ONE_TIME | `--one-time` | exit after the first sync | false |

cmd/git-sync/main.go

Lines changed: 138 additions & 54 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
"github.com/spf13/pflag"
@@ -66,6 +68,8 @@ var flRoot = pflag.String("root", envString("GIT_SYNC_ROOT", ""),
6668
"the root directory for git-sync operations, under which --link will be created")
6769
var flLink = pflag.String("link", envString("GIT_SYNC_LINK", ""),
6870
"the name of a symlink, under --root, which points to a directory in which --repo is checked out (defaults to the leaf dir of --repo)")
71+
var flErrorFile = pflag.String("error-file", envString("GIT_SYNC_ERROR_FILE", ""),
72+
"the name of a file into which errors will be written under --root (defaults to \"\", disabling error reporting)")
6973
var flPeriod = pflag.Duration("period", envDuration("GIT_SYNC_PERIOD", 10*time.Second),
7074
"how long to wait between syncs, must be >= 10ms; --wait overrides this")
7175
var flSyncTimeout = pflag.Duration("sync-timeout", envDuration("GIT_SYNC_SYNC_TIMEOUT", 120*time.Second),
@@ -139,7 +143,7 @@ func init() {
139143
pflag.CommandLine.MarkDeprecated("dest", "use --link instead")
140144
}
141145

142-
var log = glogr.New()
146+
var log *customLogger
143147

144148
// Total pull/error, summary on pull duration
145149
var (
@@ -174,6 +178,90 @@ const (
174178
submodulesOff submodulesMode = "off"
175179
)
176180

181+
type customLogger struct {
182+
logr.Logger
183+
errorFile string
184+
}
185+
186+
func (l customLogger) Error(err error, msg string, kvList ...interface{}) {
187+
l.Logger.Error(err, msg, kvList...)
188+
if l.errorFile == "" {
189+
return
190+
}
191+
payload := struct {
192+
Msg string
193+
Err string
194+
Args map[string]interface{}
195+
}{
196+
Msg: msg,
197+
Err: err.Error(),
198+
Args: map[string]interface{}{},
199+
}
200+
if len(kvList)%2 != 0 {
201+
kvList = append(kvList, "<no-value>")
202+
}
203+
for i := 0; i < len(kvList); i += 2 {
204+
k, ok := kvList[i].(string)
205+
if !ok {
206+
k = fmt.Sprintf("%v", kvList[i])
207+
}
208+
payload.Args[k] = kvList[i+1]
209+
}
210+
jb, err := json.Marshal(payload)
211+
if err != nil {
212+
l.Logger.Error(err, "can't encode error payload")
213+
content := fmt.Sprintf("%v", err)
214+
l.writeContent([]byte(content))
215+
} else {
216+
l.writeContent(jb)
217+
}
218+
}
219+
220+
// exportError exports the error to the error file if --export-error is enabled.
221+
func (l *customLogger) exportError(content string) {
222+
if l.errorFile == "" {
223+
return
224+
}
225+
l.writeContent([]byte(content))
226+
}
227+
228+
// writeContent writes the error content to the error file.
229+
func (l *customLogger) writeContent(content []byte) {
230+
tmpFile, err := ioutil.TempFile(*flRoot, "tmp-err-")
231+
if err != nil {
232+
l.Logger.Error(err, "can't create temporary error-file", "directory", *flRoot, "prefix", "tmp-err-")
233+
return
234+
}
235+
defer func() {
236+
if err := tmpFile.Close(); err != nil {
237+
l.Logger.Error(err, "can't close temporary error-file", "filename", tmpFile.Name())
238+
}
239+
}()
240+
241+
if _, err = tmpFile.Write(content); err != nil {
242+
l.Logger.Error(err, "can't write to temporary error-file", "filename", tmpFile.Name())
243+
return
244+
}
245+
246+
if err := os.Rename(tmpFile.Name(), l.errorFile); err != nil {
247+
l.Logger.Error(err, "can't rename to error-file", "temp-file", tmpFile.Name(), "error-file", l.errorFile)
248+
return
249+
}
250+
}
251+
252+
// deleteErrorFile deletes the error file.
253+
func (l *customLogger) deleteErrorFile() {
254+
if l.errorFile == "" {
255+
return
256+
}
257+
if err := os.Remove(l.errorFile); err != nil {
258+
if os.IsNotExist(err) {
259+
return
260+
}
261+
l.Logger.Error(err, "can't delete the error-file", "filename", l.errorFile)
262+
}
263+
}
264+
177265
func init() {
178266
prometheus.MustRegister(syncDuration)
179267
prometheus.MustRegister(syncCount)
@@ -203,7 +291,7 @@ func envInt(key string, def int) int {
203291
if env := os.Getenv(key); env != "" {
204292
val, err := strconv.ParseInt(env, 0, 0)
205293
if err != nil {
206-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
294+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%d\n", err, key, env, def)
207295
return def
208296
}
209297
return int(val)
@@ -215,7 +303,7 @@ func envFloat(key string, def float64) float64 {
215303
if env := os.Getenv(key); env != "" {
216304
val, err := strconv.ParseFloat(env, 64)
217305
if err != nil {
218-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
306+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%f\n", err, key, env, def)
219307
return def
220308
}
221309
return val
@@ -227,7 +315,7 @@ func envDuration(key string, def time.Duration) time.Duration {
227315
if env := os.Getenv(key); env != "" {
228316
val, err := time.ParseDuration(env)
229317
if err != nil {
230-
log.Error(err, "invalid env value, using default", "key", key, "val", os.Getenv(key), "default", def)
318+
fmt.Fprintf(os.Stderr, "WARNING: invalid env value (%v): using default, key=%s, val=%q, default=%d\n", err, key, env, def)
231319
return def
232320
}
233321
return val
@@ -239,8 +327,7 @@ func setGlogFlags() {
239327
// Force logging to stderr.
240328
stderrFlag := flag.Lookup("logtostderr")
241329
if stderrFlag == nil {
242-
fmt.Fprintf(os.Stderr, "ERROR: can't find glog flag 'logtostderr'\n")
243-
os.Exit(1)
330+
handleError(false, "ERROR: can't find glog flag 'logtostderr'")
244331
}
245332
stderrFlag.Value.Set("true")
246333

@@ -287,6 +374,12 @@ func main() {
287374
flag.CommandLine.Parse(nil) // Otherwise glog complains
288375
setGlogFlags()
289376

377+
var errorFile string
378+
if *flErrorFile != "" {
379+
errorFile = filepath.Join(*flRoot, *flErrorFile)
380+
}
381+
log = &customLogger{glogr.New(), errorFile}
382+
290383
if *flVersion {
291384
fmt.Println(version.VERSION)
292385
os.Exit(0)
@@ -302,29 +395,21 @@ func main() {
302395
}
303396

304397
if *flRepo == "" {
305-
fmt.Fprintf(os.Stderr, "ERROR: --repo must be specified\n")
306-
pflag.Usage()
307-
os.Exit(1)
398+
handleError(true, "ERROR: --repo must be specified")
308399
}
309400

310401
if *flDepth < 0 { // 0 means "no limit"
311-
fmt.Fprintf(os.Stderr, "ERROR: --depth must be greater than or equal to 0\n")
312-
pflag.Usage()
313-
os.Exit(1)
402+
handleError(true, "ERROR: --depth must be greater than or equal to 0")
314403
}
315404

316405
switch submodulesMode(*flSubmodules) {
317406
case submodulesRecursive, submodulesShallow, submodulesOff:
318407
default:
319-
fmt.Fprintf(os.Stderr, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff)
320-
pflag.Usage()
321-
os.Exit(1)
408+
handleError(true, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff)
322409
}
323410

324411
if *flRoot == "" {
325-
fmt.Fprintf(os.Stderr, "ERROR: --root must be specified\n")
326-
pflag.Usage()
327-
os.Exit(1)
412+
handleError(true, "ERROR: --root must be specified")
328413
}
329414

330415
if *flDest != "" {
@@ -335,79 +420,57 @@ func main() {
335420
*flLink = parts[len(parts)-1]
336421
}
337422
if strings.Contains(*flLink, "/") {
338-
fmt.Fprintf(os.Stderr, "ERROR: --link must not contain '/'\n")
339-
pflag.Usage()
340-
os.Exit(1)
423+
handleError(true, "ERROR: --link must not contain '/'")
341424
}
342425
if strings.HasPrefix(*flLink, ".") {
343-
fmt.Fprintf(os.Stderr, "ERROR: --link must not start with '.'\n")
344-
pflag.Usage()
345-
os.Exit(1)
426+
handleError(true, "ERROR: --link must not start with '.'")
346427
}
347428

348429
if *flWait != 0 {
349430
*flPeriod = time.Duration(int(*flWait*1000)) * time.Millisecond
350431
}
351432
if *flPeriod < 10*time.Millisecond {
352-
fmt.Fprintf(os.Stderr, "ERROR: --period must be at least 10ms\n")
353-
pflag.Usage()
354-
os.Exit(1)
433+
handleError(true, "ERROR: --period must be at least 10ms")
355434
}
356435

357436
if *flTimeout != 0 {
358437
*flSyncTimeout = time.Duration(*flTimeout) * time.Second
359438
}
360439
if *flSyncTimeout < 10*time.Millisecond {
361-
fmt.Fprintf(os.Stderr, "ERROR: --sync-timeout must be at least 10ms\n")
362-
pflag.Usage()
363-
os.Exit(1)
440+
handleError(true, "ERROR: --sync-timeout must be at least 10ms")
364441
}
365442

366443
if *flWebhookURL != "" {
367444
if *flWebhookStatusSuccess < -1 {
368-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-success-status must be a valid HTTP code or -1\n")
369-
pflag.Usage()
370-
os.Exit(1)
445+
handleError(true, "ERROR: --webhook-success-status must be a valid HTTP code or -1")
371446
}
372447
if *flWebhookTimeout < time.Second {
373-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-timeout must be at least 1s\n")
374-
pflag.Usage()
375-
os.Exit(1)
448+
handleError(true, "ERROR: --webhook-timeout must be at least 1s")
376449
}
377450
if *flWebhookBackoff < time.Second {
378-
fmt.Fprintf(os.Stderr, "ERROR: --webhook-backoff must be at least 1s\n")
379-
pflag.Usage()
380-
os.Exit(1)
451+
handleError(true, "ERROR: --webhook-backoff must be at least 1s")
381452
}
382453
}
383454

384455
if *flSSH {
385456
if *flUsername != "" {
386-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --username may be specified\n")
387-
os.Exit(1)
457+
handleError(false, "ERROR: only one of --ssh and --username may be specified")
388458
}
389459
if *flPassword != "" {
390-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --password may be specified\n")
391-
os.Exit(1)
460+
handleError(false, "ERROR: only one of --ssh and --password may be specified")
392461
}
393462
if *flAskPassURL != "" {
394-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --askpass-url may be specified\n")
395-
os.Exit(1)
463+
handleError(false, "ERROR: only one of --ssh and --askpass-url may be specified")
396464
}
397465
if *flCookieFile {
398-
fmt.Fprintf(os.Stderr, "ERROR: only one of --ssh and --cookie-file may be specified\n")
399-
os.Exit(1)
466+
handleError(false, "ERROR: only one of --ssh and --cookie-file may be specified")
400467
}
401468
if *flSSHKeyFile == "" {
402-
fmt.Fprintf(os.Stderr, "ERROR: --ssh-key-file must be specified when --ssh is specified\n")
403-
pflag.Usage()
404-
os.Exit(1)
469+
handleError(true, "ERROR: --ssh-key-file must be specified when --ssh is specified")
405470
}
406471
if *flSSHKnownHosts {
407472
if *flSSHKnownHostsFile == "" {
408-
fmt.Fprintf(os.Stderr, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified\n")
409-
pflag.Usage()
410-
os.Exit(1)
473+
handleError(true, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified")
411474
}
412475
}
413476
}
@@ -585,19 +648,22 @@ func main() {
585648

586649
if initialSync {
587650
if *flOneTime {
651+
log.deleteErrorFile()
588652
os.Exit(0)
589653
}
590654
if isHash, err := git.RevIsHash(ctx); err != nil {
591655
log.Error(err, "can't tell if rev is a git hash, exiting", "rev", git.rev)
592656
os.Exit(1)
593657
} else if isHash {
594658
log.V(0).Info("rev appears to be a git hash, no further sync needed", "rev", git.rev)
659+
log.deleteErrorFile()
595660
sleepForever()
596661
}
597662
initialSync = false
598663
}
599664

600665
failCount = 0
666+
log.deleteErrorFile()
601667
log.V(1).Info("next sync", "waitTime", flPeriod.String())
602668
cancel()
603669
time.Sleep(*flPeriod)
@@ -716,6 +782,18 @@ func sleepForever() {
716782
os.Exit(0)
717783
}
718784

785+
// handleError prints the error to the standard error, prints the usage if the `printUsage` flag is true,
786+
// exports the error to the error file and exits the process with the exit code.
787+
func handleError(printUsage bool, format string, a ...interface{}) {
788+
s := fmt.Sprintf(format, a...)
789+
fmt.Fprintln(os.Stderr, s)
790+
if printUsage {
791+
pflag.Usage()
792+
}
793+
log.exportError(s)
794+
os.Exit(1)
795+
}
796+
719797
// Put the current UID/GID into /etc/passwd so SSH can look it up. This
720798
// assumes that we have the permissions to write to it.
721799
func addUser() error {
@@ -1439,6 +1517,12 @@ OPTIONS
14391517
Use a git cookiefile (/etc/git-secret/cookie_file) for
14401518
authentication.
14411519
1520+
--error-file, $GIT_SYNC_ERROR_FILE
1521+
The name of a file (under --root) into which errors will be
1522+
written. This must be a filename, not a path, and may not start
1523+
with a period. (default: "", which means error reporting will be
1524+
disabled)
1525+
14421526
--depth <int>, $GIT_SYNC_DEPTH
14431527
Create a shallow clone with history truncated to the specified
14441528
number of commits.
@@ -1498,7 +1582,7 @@ OPTIONS
14981582
14991583
--period <duration>, $GIT_SYNC_PERIOD
15001584
How long to wait between sync attempts. This must be at least
1501-
10ms. This flag obsoletes --wait, but if --wait is specifed, it
1585+
10ms. This flag obsoletes --wait, but if --wait is specified, it
15021586
will take precedence. (default: 10s)
15031587
15041588
--repo <string>, $GIT_SYNC_REPO

0 commit comments

Comments
 (0)