Skip to content

Commit 833027a

Browse files
authored
Merge pull request #209 from tuna/success_exit_code
Allow setting success exit codes globally and for each mirror (fixes #207)
2 parents d2b3e73 + a5b72b8 commit 833027a

File tree

6 files changed

+162
-5
lines changed

6 files changed

+162
-5
lines changed

worker/base_provider.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ type baseProvider struct {
1919
timeout time.Duration
2020
isMaster bool
2121

22-
cmd *cmdJob
23-
logFileFd *os.File
24-
isRunning atomic.Value
22+
cmd *cmdJob
23+
logFileFd *os.File
24+
isRunning atomic.Value
25+
successExitCodes []int
2526

2627
cgroup *cgroupHook
2728
zfs *zfsHook
@@ -186,3 +187,18 @@ func (p *baseProvider) Terminate() error {
186187
func (p *baseProvider) DataSize() string {
187188
return ""
188189
}
190+
191+
func (p *baseProvider) SetSuccessExitCodes(codes []int) {
192+
if codes == nil {
193+
p.successExitCodes = []int{}
194+
} else {
195+
p.successExitCodes = codes
196+
}
197+
}
198+
199+
func (p *baseProvider) GetSuccessExitCodes() []int {
200+
if p.successExitCodes == nil {
201+
return []int{}
202+
}
203+
return p.successExitCodes
204+
}

worker/config.go

+6
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type globalConfig struct {
6363

6464
ExecOnSuccess []string `toml:"exec_on_success"`
6565
ExecOnFailure []string `toml:"exec_on_failure"`
66+
67+
// merged with mirror-specific options. make sure you know what you are doing!
68+
SuccessExitCodes []int `toml:"dangerous_global_success_exit_codes"`
6669
}
6770

6871
type managerConfig struct {
@@ -169,6 +172,9 @@ type mirrorConfig struct {
169172
ExecOnSuccessExtra []string `toml:"exec_on_success_extra"`
170173
ExecOnFailureExtra []string `toml:"exec_on_failure_extra"`
171174

175+
// will be merged with global option
176+
SuccessExitCodes []int `toml:"success_exit_codes"`
177+
172178
Command string `toml:"command"`
173179
FailOnMatch string `toml:"fail_on_match"`
174180
SizePattern string `toml:"size_pattern"`

worker/config_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -521,4 +521,60 @@ rsync_options = ["--local"]
521521
"--local", // from mirror.rsync_options
522522
})
523523
})
524+
525+
Convey("success_exit_codes should work globally and per mirror", t, func() {
526+
tmpfile, err := os.CreateTemp("", "tunasync")
527+
So(err, ShouldEqual, nil)
528+
defer os.Remove(tmpfile.Name())
529+
530+
cfgBlob1 := `
531+
[global]
532+
name = "test_worker"
533+
log_dir = "/var/log/tunasync/{{.Name}}"
534+
mirror_dir = "/data/mirrors"
535+
concurrent = 10
536+
interval = 240
537+
retry = 3
538+
timeout = 86400
539+
dangerous_global_success_exit_codes = [10, 20]
540+
541+
[manager]
542+
api_base = "https://127.0.0.1:5000"
543+
token = "some_token"
544+
545+
[server]
546+
hostname = "worker1.example.com"
547+
listen_addr = "127.0.0.1"
548+
listen_port = 6000
549+
ssl_cert = "/etc/tunasync.d/worker1.cert"
550+
ssl_key = "/etc/tunasync.d/worker1.key"
551+
552+
[[mirrors]]
553+
name = "foo"
554+
provider = "rsync"
555+
upstream = "rsync://foo.bar/"
556+
interval = 720
557+
retry = 2
558+
timeout = 3600
559+
mirror_dir = "/data/foo"
560+
success_exit_codes = [30, 40]
561+
`
562+
563+
err = os.WriteFile(tmpfile.Name(), []byte(cfgBlob1), 0644)
564+
So(err, ShouldEqual, nil)
565+
defer tmpfile.Close()
566+
567+
cfg, err := LoadConfig(tmpfile.Name())
568+
So(err, ShouldBeNil)
569+
570+
providers := map[string]mirrorProvider{}
571+
for _, m := range cfg.Mirrors {
572+
p := newMirrorProvider(m, cfg)
573+
providers[p.Name()] = p
574+
}
575+
576+
p, ok := providers["foo"].(*rsyncProvider)
577+
So(ok, ShouldBeTrue)
578+
So(p.successExitCodes, ShouldResemble, []int{10, 20, 30, 40})
579+
})
524580
}

worker/provider.go

+16
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ type mirrorProvider interface {
6060
ExitContext() *Context
6161
// return context
6262
Context() *Context
63+
64+
// set in newMirrorProvider, used by cmdJob.Wait
65+
SetSuccessExitCodes(codes []int)
66+
GetSuccessExitCodes() []int
6367
}
6468

6569
// newProvider creates a mirrorProvider instance
@@ -249,5 +253,17 @@ func newMirrorProvider(mirror mirrorConfig, cfg *Config) mirrorProvider {
249253
}
250254
addHookFromCmdList(mirror.ExecOnFailureExtra, execOnFailure)
251255

256+
successExitCodes := []int{}
257+
if cfg.Global.SuccessExitCodes != nil {
258+
successExitCodes = append(successExitCodes, cfg.Global.SuccessExitCodes...)
259+
}
260+
if mirror.SuccessExitCodes != nil {
261+
successExitCodes = append(successExitCodes, mirror.SuccessExitCodes...)
262+
}
263+
if len(successExitCodes) > 0 {
264+
logger.Infof("Non-zero success exit codes set for mirror %s: %v", mirror.Name, successExitCodes)
265+
provider.SetSuccessExitCodes(successExitCodes)
266+
}
267+
252268
return provider
253269
}

worker/provider_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,59 @@ sleep 10
552552
So(provider.DataSize(), ShouldBeEmpty)
553553
})
554554
})
555+
Convey("Command Provider with successExitCodes should work", t, func(ctx C) {
556+
tmpDir, err := os.MkdirTemp("", "tunasync")
557+
defer os.RemoveAll(tmpDir)
558+
So(err, ShouldBeNil)
559+
scriptFile := filepath.Join(tmpDir, "cmd.sh")
560+
tmpFile := filepath.Join(tmpDir, "log_file")
561+
562+
c := cmdConfig{
563+
name: "tuna-cmd",
564+
upstreamURL: "http://mirrors.tuna.moe/",
565+
command: "bash " + scriptFile,
566+
workingDir: tmpDir,
567+
logDir: tmpDir,
568+
logFile: tmpFile,
569+
interval: 600 * time.Second,
570+
}
571+
572+
provider, err := newCmdProvider(c)
573+
provider.SetSuccessExitCodes([]int{199, 200})
574+
So(err, ShouldBeNil)
575+
576+
So(provider.Type(), ShouldEqual, provCommand)
577+
So(provider.Name(), ShouldEqual, c.name)
578+
So(provider.WorkingDir(), ShouldEqual, c.workingDir)
579+
So(provider.LogDir(), ShouldEqual, c.logDir)
580+
So(provider.LogFile(), ShouldEqual, c.logFile)
581+
So(provider.Interval(), ShouldEqual, c.interval)
582+
So(provider.GetSuccessExitCodes(), ShouldResemble, []int{199, 200})
583+
584+
Convey("Command exits with configured successExitCodes", func() {
585+
scriptContent := `exit 199`
586+
err = os.WriteFile(scriptFile, []byte(scriptContent), 0755)
587+
So(err, ShouldBeNil)
588+
readedScriptContent, err := os.ReadFile(scriptFile)
589+
So(err, ShouldBeNil)
590+
So(readedScriptContent, ShouldResemble, []byte(scriptContent))
591+
592+
err = provider.Run(make(chan empty, 1))
593+
So(err, ShouldBeNil)
594+
})
595+
596+
Convey("Command exits with unknown exit code", func() {
597+
scriptContent := `exit 201`
598+
err = os.WriteFile(scriptFile, []byte(scriptContent), 0755)
599+
So(err, ShouldBeNil)
600+
readedScriptContent, err := os.ReadFile(scriptFile)
601+
So(err, ShouldBeNil)
602+
So(readedScriptContent, ShouldResemble, []byte(scriptContent))
603+
604+
err = provider.Run(make(chan empty, 1))
605+
So(err, ShouldNotBeNil)
606+
})
607+
})
555608
}
556609

557610
func TestTwoStageRsyncProvider(t *testing.T) {

worker/runner.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"os/exec"
8+
"slices"
89
"strings"
910
"sync"
1011
"syscall"
@@ -171,9 +172,18 @@ func (c *cmdJob) Wait() error {
171172
return c.retErr
172173
default:
173174
err := c.cmd.Wait()
174-
c.retErr = err
175175
close(c.finished)
176-
return err
176+
if err != nil {
177+
code := err.(*exec.ExitError).ExitCode()
178+
allowedCodes := c.provider.GetSuccessExitCodes()
179+
if slices.Contains(allowedCodes, code) {
180+
// process exited with non-success status
181+
logger.Infof("Command %s exited with code %d: treated as success (allowed: %v)", c.cmd.Args, code, allowedCodes)
182+
} else {
183+
c.retErr = err
184+
}
185+
}
186+
return c.retErr
177187
}
178188
}
179189

0 commit comments

Comments
 (0)