@@ -21,6 +21,7 @@ package main // import "k8s.io/git-sync/cmd/git-sync"
21
21
import (
22
22
"bytes"
23
23
"context"
24
+ "encoding/json"
24
25
"flag"
25
26
"fmt"
26
27
"io/ioutil"
@@ -37,6 +38,7 @@ import (
37
38
"time"
38
39
39
40
"github.com/go-logr/glogr"
41
+ "github.com/go-logr/logr"
40
42
"github.com/prometheus/client_golang/prometheus"
41
43
"github.com/prometheus/client_golang/prometheus/promhttp"
42
44
"github.com/spf13/pflag"
@@ -66,6 +68,8 @@ var flRoot = pflag.String("root", envString("GIT_SYNC_ROOT", ""),
66
68
"the root directory for git-sync operations, under which --link will be created" )
67
69
var flLink = pflag .String ("link" , envString ("GIT_SYNC_LINK" , "" ),
68
70
"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)" )
69
73
var flPeriod = pflag .Duration ("period" , envDuration ("GIT_SYNC_PERIOD" , 10 * time .Second ),
70
74
"how long to wait between syncs, must be >= 10ms; --wait overrides this" )
71
75
var flSyncTimeout = pflag .Duration ("sync-timeout" , envDuration ("GIT_SYNC_SYNC_TIMEOUT" , 120 * time .Second ),
@@ -139,7 +143,7 @@ func init() {
139
143
pflag .CommandLine .MarkDeprecated ("dest" , "use --link instead" )
140
144
}
141
145
142
- var log = glogr . New ()
146
+ var log * customLogger
143
147
144
148
// Total pull/error, summary on pull duration
145
149
var (
@@ -174,6 +178,90 @@ const (
174
178
submodulesOff submodulesMode = "off"
175
179
)
176
180
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
+
177
265
func init () {
178
266
prometheus .MustRegister (syncDuration )
179
267
prometheus .MustRegister (syncCount )
@@ -203,7 +291,7 @@ func envInt(key string, def int) int {
203
291
if env := os .Getenv (key ); env != "" {
204
292
val , err := strconv .ParseInt (env , 0 , 0 )
205
293
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 )
207
295
return def
208
296
}
209
297
return int (val )
@@ -215,7 +303,7 @@ func envFloat(key string, def float64) float64 {
215
303
if env := os .Getenv (key ); env != "" {
216
304
val , err := strconv .ParseFloat (env , 64 )
217
305
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 )
219
307
return def
220
308
}
221
309
return val
@@ -227,7 +315,7 @@ func envDuration(key string, def time.Duration) time.Duration {
227
315
if env := os .Getenv (key ); env != "" {
228
316
val , err := time .ParseDuration (env )
229
317
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 )
231
319
return def
232
320
}
233
321
return val
@@ -239,8 +327,7 @@ func setGlogFlags() {
239
327
// Force logging to stderr.
240
328
stderrFlag := flag .Lookup ("logtostderr" )
241
329
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'" )
244
331
}
245
332
stderrFlag .Value .Set ("true" )
246
333
@@ -287,6 +374,12 @@ func main() {
287
374
flag .CommandLine .Parse (nil ) // Otherwise glog complains
288
375
setGlogFlags ()
289
376
377
+ var errorFile string
378
+ if * flErrorFile != "" {
379
+ errorFile = filepath .Join (* flRoot , * flErrorFile )
380
+ }
381
+ log = & customLogger {glogr .New (), errorFile }
382
+
290
383
if * flVersion {
291
384
fmt .Println (version .VERSION )
292
385
os .Exit (0 )
@@ -302,29 +395,21 @@ func main() {
302
395
}
303
396
304
397
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" )
308
399
}
309
400
310
401
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" )
314
403
}
315
404
316
405
switch submodulesMode (* flSubmodules ) {
317
406
case submodulesRecursive , submodulesShallow , submodulesOff :
318
407
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 )
322
409
}
323
410
324
411
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" )
328
413
}
329
414
330
415
if * flDest != "" {
@@ -335,79 +420,57 @@ func main() {
335
420
* flLink = parts [len (parts )- 1 ]
336
421
}
337
422
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 '/'" )
341
424
}
342
425
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 '.'" )
346
427
}
347
428
348
429
if * flWait != 0 {
349
430
* flPeriod = time .Duration (int (* flWait * 1000 )) * time .Millisecond
350
431
}
351
432
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" )
355
434
}
356
435
357
436
if * flTimeout != 0 {
358
437
* flSyncTimeout = time .Duration (* flTimeout ) * time .Second
359
438
}
360
439
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" )
364
441
}
365
442
366
443
if * flWebhookURL != "" {
367
444
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" )
371
446
}
372
447
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" )
376
449
}
377
450
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" )
381
452
}
382
453
}
383
454
384
455
if * flSSH {
385
456
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" )
388
458
}
389
459
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" )
392
461
}
393
462
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" )
396
464
}
397
465
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" )
400
467
}
401
468
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" )
405
470
}
406
471
if * flSSHKnownHosts {
407
472
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" )
411
474
}
412
475
}
413
476
}
@@ -585,19 +648,22 @@ func main() {
585
648
586
649
if initialSync {
587
650
if * flOneTime {
651
+ log .deleteErrorFile ()
588
652
os .Exit (0 )
589
653
}
590
654
if isHash , err := git .RevIsHash (ctx ); err != nil {
591
655
log .Error (err , "can't tell if rev is a git hash, exiting" , "rev" , git .rev )
592
656
os .Exit (1 )
593
657
} else if isHash {
594
658
log .V (0 ).Info ("rev appears to be a git hash, no further sync needed" , "rev" , git .rev )
659
+ log .deleteErrorFile ()
595
660
sleepForever ()
596
661
}
597
662
initialSync = false
598
663
}
599
664
600
665
failCount = 0
666
+ log .deleteErrorFile ()
601
667
log .V (1 ).Info ("next sync" , "waitTime" , flPeriod .String ())
602
668
cancel ()
603
669
time .Sleep (* flPeriod )
@@ -716,6 +782,18 @@ func sleepForever() {
716
782
os .Exit (0 )
717
783
}
718
784
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
+
719
797
// Put the current UID/GID into /etc/passwd so SSH can look it up. This
720
798
// assumes that we have the permissions to write to it.
721
799
func addUser () error {
@@ -1439,6 +1517,12 @@ OPTIONS
1439
1517
Use a git cookiefile (/etc/git-secret/cookie_file) for
1440
1518
authentication.
1441
1519
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
+
1442
1526
--depth <int>, $GIT_SYNC_DEPTH
1443
1527
Create a shallow clone with history truncated to the specified
1444
1528
number of commits.
@@ -1498,7 +1582,7 @@ OPTIONS
1498
1582
1499
1583
--period <duration>, $GIT_SYNC_PERIOD
1500
1584
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
1502
1586
will take precedence. (default: 10s)
1503
1587
1504
1588
--repo <string>, $GIT_SYNC_REPO
0 commit comments