@@ -21,6 +21,7 @@ package main // import "k8s.io/git-sync/cmd/git-sync"
2121import (
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" )
6163var 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)" )
6367var flWait = flag .Float64 ("wait" , envFloat ("GIT_SYNC_WAIT" , 1 ),
6468 "the number of seconds between syncs" )
6569var 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
119123var 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
125129var (
@@ -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+
158246func 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.
522595func addUser () error {
0 commit comments