@@ -2,62 +2,120 @@ package core
2
2
3
3
import (
4
4
"bytes"
5
+ "context"
5
6
"fmt"
6
- "os/exec"
7
+ "strings"
8
+
9
+ "github.com/github/git-bundle-server/internal/cmd"
10
+ "github.com/github/git-bundle-server/internal/common"
11
+ "github.com/github/git-bundle-server/internal/log"
12
+ "github.com/github/git-bundle-server/internal/utils"
13
+ )
14
+
15
+ type cronSchedule string
16
+
17
+ const (
18
+ CronDaily cronSchedule = "0 0 * * *"
19
+ CronWeekly cronSchedule = "0 0 0 * *"
7
20
)
8
21
9
- func GetCrontabCommand (args ... string ) (* exec.Cmd , error ) {
10
- crontab , err := exec .LookPath ("crontab" )
22
+ type CronScheduler interface {
23
+ AddJob (ctx context.Context , schedule cronSchedule ,
24
+ exePath string , args []string ) error
25
+ }
26
+
27
+ type cronScheduler struct {
28
+ logger log.TraceLogger
29
+ user common.UserProvider
30
+ cmdExec cmd.CommandExecutor
31
+ fileSystem common.FileSystem
32
+ }
33
+
34
+ func NewCronScheduler (
35
+ l log.TraceLogger ,
36
+ u common.UserProvider ,
37
+ c cmd.CommandExecutor ,
38
+ fs common.FileSystem ,
39
+ ) CronScheduler {
40
+ return & cronScheduler {
41
+ logger : l ,
42
+ user : u ,
43
+ cmdExec : c ,
44
+ fileSystem : fs ,
45
+ }
46
+ }
47
+
48
+ func (c * cronScheduler ) loadExistingSchedule (ctx context.Context ) ([]byte , error ) {
49
+ buffer := bytes.Buffer {}
50
+ exitCode , err := c .cmdExec .Run (ctx , "crontab" , []string {"-l" }, cmd .Stdout (& buffer ))
11
51
if err != nil {
12
- return nil , fmt .Errorf ("failed to find 'crontab' on the path: %w" , err )
52
+ return nil , c .logger .Error (ctx , err )
53
+ } else if exitCode != 0 {
54
+ return nil , c .logger .Errorf (ctx , "'crontab' exited with status %d" , exitCode )
13
55
}
14
56
15
- cmd := exec .Command (crontab , args ... )
16
- return cmd , nil
57
+ return buffer .Bytes (), nil
17
58
}
18
59
19
- func LoadExistingSchedule () ([] byte , error ) {
20
- cmd , err := GetCrontabCommand ( "-l" )
60
+ func ( c * cronScheduler ) commitCronSchedule ( ctx context. Context , filename string ) error {
61
+ exitCode , err := c . cmdExec . RunQuiet ( ctx , "crontab" , filename )
21
62
if err != nil {
22
- return nil , err
63
+ return c .logger .Error (ctx , err )
64
+ } else if exitCode != 0 {
65
+ return c .logger .Errorf (ctx , "'crontab' exited with status %d" , exitCode )
23
66
}
24
67
25
- buffer := bytes. Buffer {}
26
- cmd . Stdout = & buffer
68
+ return nil
69
+ }
27
70
28
- errorBuffer := bytes.Buffer {}
29
- cmd .Stderr = & errorBuffer
71
+ func (c * cronScheduler ) AddJob (ctx context.Context ,
72
+ schedule cronSchedule ,
73
+ exePath string ,
74
+ args []string ,
75
+ ) error {
76
+ newSchedule := fmt .Sprintf ("%s \" %s\" %s" ,
77
+ schedule ,
78
+ exePath ,
79
+ utils .Map (args , func (s string ) string { return "\" " + s + "\" " }),
80
+ )
30
81
31
- err = cmd . Start ( )
82
+ scheduleBytes , err := c . loadExistingSchedule ( ctx )
32
83
if err != nil {
33
- return nil , fmt . Errorf ("crontab failed to start : %w" , err )
84
+ return c . logger . Errorf (ctx , " failed to get existing cron schedule : %w" , err )
34
85
}
35
86
36
- err = cmd .Wait ()
37
- if err != nil {
38
- return nil , fmt .Errorf ("crontab returned a failure: %w\n stderr: %s" , err , errorBuffer .String ())
87
+ scheduleStr := string (scheduleBytes )
88
+
89
+ // TODO: Use comments to indicate a "region" where our schedule
90
+ // is set, so we can remove the entire region even if we update
91
+ // the schedule in the future.
92
+ if strings .Contains (scheduleStr , newSchedule ) {
93
+ // We already have this schedule, so skip modifying
94
+ // the crontab schedule.
95
+ return nil
39
96
}
40
97
41
- return buffer .Bytes (), nil
42
- }
98
+ scheduleBytes = append (scheduleBytes , []byte (schedule )... )
43
99
44
- func CommitCronSchedule (filename string ) error {
45
- cmd , err := GetCrontabCommand (filename )
100
+ user , err := c .user .CurrentUser ()
46
101
if err != nil {
47
- return err
102
+ return c . logger . Error ( ctx , err )
48
103
}
104
+ scheduleFile := CrontabFile (user )
49
105
50
- errorBuffer := bytes.Buffer {}
51
- cmd .Stderr = & errorBuffer
106
+ err = c .fileSystem .WriteFile (scheduleFile , scheduleBytes )
107
+ if err != nil {
108
+ return c .logger .Errorf (ctx , "failed to write new cron schedule to temp file: %w" , err )
109
+ }
52
110
53
- err = cmd . Start ( )
111
+ err = c . commitCronSchedule ( ctx , scheduleFile )
54
112
if err != nil {
55
- return fmt . Errorf ("crontab failed to start : %w" , err )
113
+ return c . logger . Errorf (ctx , " failed to commit new cron schedule : %w" , err )
56
114
}
57
115
58
- err = cmd . Wait ( )
116
+ _ , err = c . fileSystem . DeleteFile ( scheduleFile )
59
117
if err != nil {
60
- return fmt . Errorf ("crontab returned a failure: %w \n stderr : %s " , err , errorBuffer . String () )
118
+ return c . logger . Errorf (ctx , "failed to clear schedule temp file : %w " , err )
61
119
}
62
120
63
121
return nil
0 commit comments