Skip to content

Commit d928300

Browse files
Merge pull request #5: git-bundle-server CLI 4: complete basic subcommands
* Add `crontab` integration in the `init` subcommand. Initializes daily updates of the bundle lists. * Add `start`, `stop`, and `delete` subcommands.
2 parents acfad6f + a484643 commit d928300

File tree

9 files changed

+251
-2
lines changed

9 files changed

+251
-2
lines changed

Diff for: cmd/git-bundle-server/cron.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"github.com/github/git-bundle-server/internal/core"
9+
)
10+
11+
func SetCronSchedule() error {
12+
pathToExec, err := os.Executable()
13+
if err != nil {
14+
return fmt.Errorf("failed to get executable: %w", err)
15+
}
16+
17+
dailySchedule := "0 0 * * * \"" + pathToExec + "\" update-all\n"
18+
19+
scheduleBytes, err := core.LoadExistingSchedule()
20+
if err != nil {
21+
return fmt.Errorf("failed to get existing cron schedule: %w", err)
22+
}
23+
24+
scheduleStr := string(scheduleBytes)
25+
26+
// TODO: Use comments to indicate a "region" where our schedule
27+
// is set, so we can remove the entire region even if we update
28+
// the schedule in the future.
29+
if strings.Contains(scheduleStr, dailySchedule) {
30+
// We already have this schedule, so skip modifying
31+
// the crontab schedule.
32+
return nil
33+
}
34+
35+
scheduleBytes = append(scheduleBytes, []byte(dailySchedule)...)
36+
scheduleFile := core.CrontabFile()
37+
38+
err = os.WriteFile(scheduleFile, scheduleBytes, 0o600)
39+
if err != nil {
40+
return fmt.Errorf("failed to write new cron schedule to temp file: %w", err)
41+
}
42+
43+
err = core.CommitCronSchedule(scheduleFile)
44+
if err != nil {
45+
return fmt.Errorf("failed to commit new cron schedule: %w", err)
46+
}
47+
48+
err = os.Remove(scheduleFile)
49+
if err != nil {
50+
return fmt.Errorf("failed to clear schedule file: %w", err)
51+
}
52+
53+
return nil
54+
}

Diff for: cmd/git-bundle-server/delete.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"os"
6+
7+
"github.com/github/git-bundle-server/internal/core"
8+
)
9+
10+
type Delete struct{}
11+
12+
func (Delete) subcommand() string {
13+
return "delete"
14+
}
15+
16+
func (Delete) run(args []string) error {
17+
if len(args) < 1 {
18+
return errors.New("usage: git-bundle-server delete <route>")
19+
}
20+
21+
route := args[0]
22+
23+
repo, err := core.CreateRepository(route)
24+
if err != nil {
25+
return err
26+
}
27+
28+
err = core.RemoveRoute(route)
29+
if err != nil {
30+
return err
31+
}
32+
33+
err = os.RemoveAll(repo.WebDir)
34+
if err != nil {
35+
return err
36+
}
37+
38+
err = os.RemoveAll(repo.RepoDir)
39+
if err != nil {
40+
return err
41+
}
42+
43+
return nil
44+
}

Diff for: cmd/git-bundle-server/init.go

+2
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,7 @@ func (Init) run(args []string) error {
6363
return fmt.Errorf("failed to write bundle list: %w", listErr)
6464
}
6565

66+
SetCronSchedule()
67+
6668
return nil
6769
}

Diff for: cmd/git-bundle-server/start.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
8+
"github.com/github/git-bundle-server/internal/core"
9+
)
10+
11+
type Start struct{}
12+
13+
func (Start) subcommand() string {
14+
return "start"
15+
}
16+
17+
func (Start) run(args []string) error {
18+
if len(args) < 1 {
19+
return errors.New("usage: git-bundle-server start <route>")
20+
}
21+
22+
route := args[0]
23+
24+
// CreateRepository registers the route.
25+
repo, err := core.CreateRepository(route)
26+
if err != nil {
27+
return err
28+
}
29+
30+
_, err = os.ReadDir(repo.RepoDir)
31+
if err != nil {
32+
return fmt.Errorf("route '%s' appears to have been deleted; use 'init' instead", route)
33+
}
34+
35+
// Make sure we have the global schedule running.
36+
SetCronSchedule()
37+
38+
return nil
39+
}

Diff for: cmd/git-bundle-server/stop.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
6+
"github.com/github/git-bundle-server/internal/core"
7+
)
8+
9+
type Stop struct{}
10+
11+
func (Stop) subcommand() string {
12+
return "stop"
13+
}
14+
15+
func (Stop) run(args []string) error {
16+
if len(args) < 1 {
17+
return errors.New("usage: git-bundle-server stop <route>")
18+
}
19+
20+
route := args[0]
21+
22+
return core.RemoveRoute(route)
23+
}

Diff for: cmd/git-bundle-server/subcommand.go

+3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ type Subcommand interface {
77

88
func all() []Subcommand {
99
return []Subcommand{
10+
Delete{},
1011
Init{},
12+
Start{},
13+
Stop{},
1114
Update{},
1215
UpdateAll{},
1316
}

Diff for: internal/core/cron.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package core
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os/exec"
7+
)
8+
9+
func GetCrontabCommand(args ...string) (*exec.Cmd, error) {
10+
crontab, err := exec.LookPath("crontab")
11+
if err != nil {
12+
return nil, fmt.Errorf("failed to find 'crontab' on the path: %w", err)
13+
}
14+
15+
cmd := exec.Command(crontab, args...)
16+
return cmd, nil
17+
}
18+
19+
func LoadExistingSchedule() ([]byte, error) {
20+
cmd, err := GetCrontabCommand("-l")
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
buffer := bytes.Buffer{}
26+
cmd.Stdout = &buffer
27+
28+
errorBuffer := bytes.Buffer{}
29+
cmd.Stderr = &errorBuffer
30+
31+
err = cmd.Start()
32+
if err != nil {
33+
return nil, fmt.Errorf("crontab failed to start: %w", err)
34+
}
35+
36+
err = cmd.Wait()
37+
if err != nil {
38+
return nil, fmt.Errorf("crontab returned a failure: %w\nstderr: %s", err, errorBuffer.String())
39+
}
40+
41+
return buffer.Bytes(), nil
42+
}
43+
44+
func CommitCronSchedule(filename string) error {
45+
cmd, err := GetCrontabCommand(filename)
46+
if err != nil {
47+
return err
48+
}
49+
50+
errorBuffer := bytes.Buffer{}
51+
cmd.Stderr = &errorBuffer
52+
53+
err = cmd.Start()
54+
if err != nil {
55+
return fmt.Errorf("crontab failed to start: %w", err)
56+
}
57+
58+
err = cmd.Wait()
59+
if err != nil {
60+
return fmt.Errorf("crontab returned a failure: %w\nstderr: %s", err, errorBuffer.String())
61+
}
62+
63+
return nil
64+
}

Diff for: internal/core/paths.go

+4
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ func webroot() string {
1919
func reporoot() string {
2020
return bundleroot() + "git/"
2121
}
22+
23+
func CrontabFile() string {
24+
return bundleroot() + "cron-schedule"
25+
}

Diff for: internal/core/repo.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,22 @@ func CreateRepository(route string) (*Repository, error) {
4949
return &repo, nil
5050
}
5151

52+
func RemoveRoute(route string) error {
53+
repos, err := GetRepositories()
54+
if err != nil {
55+
return fmt.Errorf("failed to parse routes file")
56+
}
57+
58+
_, contains := repos[route]
59+
if !contains {
60+
return fmt.Errorf("route '%s' is not registered", route)
61+
}
62+
63+
delete(repos, route)
64+
65+
return WriteRouteFile(repos)
66+
}
67+
5268
func WriteRouteFile(repos map[string]Repository) error {
5369
dir := bundleroot()
5470
routefile := dir + "/routes"
@@ -59,7 +75,7 @@ func WriteRouteFile(repos map[string]Repository) error {
5975
contents = contents + routes + "\n"
6076
}
6177

62-
return os.WriteFile(routefile, []byte(contents), 0600)
78+
return os.WriteFile(routefile, []byte(contents), 0o600)
6379
}
6480

6581
func GetRepositories() (map[string]Repository, error) {
@@ -68,7 +84,7 @@ func GetRepositories() (map[string]Repository, error) {
6884
dir := bundleroot()
6985
routefile := dir + "/routes"
7086

71-
file, err := os.OpenFile(routefile, os.O_RDONLY|os.O_CREATE, 0600)
87+
file, err := os.OpenFile(routefile, os.O_RDONLY|os.O_CREATE, 0o600)
7288
if err != nil {
7389
// Assume that the file doesn't exist?
7490
return repos, nil

0 commit comments

Comments
 (0)