Skip to content

Commit 8ea4492

Browse files
committed
Export the error details to an error file
The current git-sync process outputs the error information to standard out, which is inaccessible from outside the container. Users have to dump the logs using kubectl logs in order to check the error details in the git-sync process. This commit exports the error details to a file, which provides users the capability to check the errors directly from other sidecar containers. proposal: #326
1 parent 50917bf commit 8ea4492

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)