镜像自地址
https://github.com/tuna/tunasync.git
已同步 2025-12-08 23:46:47 +00:00
比较提交
9 次代码提交
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
c99916cc2a | ||
|
|
9eb72c5db0 | ||
|
|
b490c22984 | ||
|
|
ae5ff25d20 | ||
|
|
365f49e6d3 | ||
|
|
fddb793ca1 | ||
|
|
c41d7a4038 | ||
|
|
8b0ef2bb53 | ||
|
|
b25be80670 |
7
.github/workflows/tunasync.yml
vendored
7
.github/workflows/tunasync.yml
vendored
@@ -36,10 +36,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup test dependencies
|
- name: Setup test dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt-get update
|
||||||
sudo apt install -y cgroup-bin
|
sudo apt-get install -y cgroup-bin
|
||||||
/usr/bin/docker pull alpine
|
docker pull alpine:3.8
|
||||||
/usr/bin/docker images
|
|
||||||
lssubsys -am
|
lssubsys -am
|
||||||
sudo cgcreate -a $USER -t $USER -g cpu:tunasync
|
sudo cgcreate -a $USER -t $USER -g cpu:tunasync
|
||||||
sudo cgcreate -a $USER -t $USER -g memory:tunasync
|
sudo cgcreate -a $USER -t $USER -g memory:tunasync
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
const Version string = "0.4.1"
|
const Version string = "0.4.3"
|
||||||
|
|||||||
127
worker/docker_test.go
普通文件
127
worker/docker_test.go
普通文件
@@ -0,0 +1,127 @@
|
|||||||
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codeskyblue/go-sh"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdRun(p string, args []string) {
|
||||||
|
cmd := exec.Command(p, args...)
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Debugf("cmdRun failed %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Debugf("cmdRun: ", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDockerByName(name string) (string, error) {
|
||||||
|
// docker ps -f 'name=$name' --format '{{.Names}}'
|
||||||
|
out, err := sh.Command(
|
||||||
|
"docker", "ps", "-a",
|
||||||
|
"--filter", "name="+name,
|
||||||
|
"--format", "{{.Names}}",
|
||||||
|
).Output()
|
||||||
|
if err == nil {
|
||||||
|
logger.Debugf("docker ps: '%s'", string(out))
|
||||||
|
}
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocker(t *testing.T) {
|
||||||
|
Convey("Docker Should Work", t, func(ctx C) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "tunasync")
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
cmdScript := filepath.Join(tmpDir, "cmd.sh")
|
||||||
|
tmpFile := filepath.Join(tmpDir, "log_file")
|
||||||
|
expectedOutput := "HELLO_WORLD"
|
||||||
|
|
||||||
|
c := cmdConfig{
|
||||||
|
name: "tuna-docker",
|
||||||
|
upstreamURL: "http://mirrors.tuna.moe/",
|
||||||
|
command: "/bin/cmd.sh",
|
||||||
|
workingDir: tmpDir,
|
||||||
|
logDir: tmpDir,
|
||||||
|
logFile: tmpFile,
|
||||||
|
interval: 600 * time.Second,
|
||||||
|
env: map[string]string{
|
||||||
|
"TEST_CONTENT": expectedOutput,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdScriptContent := `#!/bin/sh
|
||||||
|
echo ${TEST_CONTENT}
|
||||||
|
sleep 20
|
||||||
|
`
|
||||||
|
err = ioutil.WriteFile(cmdScript, []byte(cmdScriptContent), 0755)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
provider, err := newCmdProvider(c)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
d := &dockerHook{
|
||||||
|
emptyHook: emptyHook{
|
||||||
|
provider: provider,
|
||||||
|
},
|
||||||
|
image: "alpine:3.8",
|
||||||
|
volumes: []string{
|
||||||
|
fmt.Sprintf("%s:%s", cmdScript, "/bin/cmd.sh"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
provider.AddHook(d)
|
||||||
|
So(provider.Docker(), ShouldNotBeNil)
|
||||||
|
|
||||||
|
err = d.preExec()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cmdRun("docker", []string{"images"})
|
||||||
|
exitedErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
err = provider.Run()
|
||||||
|
logger.Debugf("provider.Run() exited")
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("provider.Run() failed: %v", err)
|
||||||
|
}
|
||||||
|
exitedErr <- err
|
||||||
|
}()
|
||||||
|
cmdRun("ps", []string{"aux"})
|
||||||
|
|
||||||
|
// Wait for docker running
|
||||||
|
time.Sleep(8 * time.Second)
|
||||||
|
|
||||||
|
cmdRun("ps", []string{"aux"})
|
||||||
|
|
||||||
|
// assert container running
|
||||||
|
names, err := getDockerByName(d.Name())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
// So(names, ShouldEqual, d.Name()+"\n")
|
||||||
|
|
||||||
|
err = provider.Terminate()
|
||||||
|
// So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cmdRun("ps", []string{"aux"})
|
||||||
|
<-exitedErr
|
||||||
|
|
||||||
|
// container should be terminated and removed
|
||||||
|
names, err = getDockerByName(d.Name())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(names, ShouldEqual, "")
|
||||||
|
|
||||||
|
// check log content
|
||||||
|
loggedContent, err := ioutil.ReadFile(provider.LogFile())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(loggedContent), ShouldEqual, expectedOutput+"\n")
|
||||||
|
|
||||||
|
d.postExec()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -135,6 +135,7 @@ func newMirrorProvider(mirror mirrorConfig, cfg *Config) mirrorProvider {
|
|||||||
excludeFile: mirror.ExcludeFile,
|
excludeFile: mirror.ExcludeFile,
|
||||||
extraOptions: mirror.RsyncOptions,
|
extraOptions: mirror.RsyncOptions,
|
||||||
overriddenOptions: mirror.RsyncOverride,
|
overriddenOptions: mirror.RsyncOverride,
|
||||||
|
rsyncEnv: mirror.Env,
|
||||||
workingDir: mirrorDir,
|
workingDir: mirrorDir,
|
||||||
logDir: logDir,
|
logDir: logDir,
|
||||||
logFile: filepath.Join(logDir, "latest.log"),
|
logFile: filepath.Join(logDir, "latest.log"),
|
||||||
@@ -159,6 +160,7 @@ func newMirrorProvider(mirror mirrorConfig, cfg *Config) mirrorProvider {
|
|||||||
password: mirror.Password,
|
password: mirror.Password,
|
||||||
excludeFile: mirror.ExcludeFile,
|
excludeFile: mirror.ExcludeFile,
|
||||||
extraOptions: mirror.RsyncOptions,
|
extraOptions: mirror.RsyncOptions,
|
||||||
|
rsyncEnv: mirror.Env,
|
||||||
workingDir: mirrorDir,
|
workingDir: mirrorDir,
|
||||||
logDir: logDir,
|
logDir: logDir,
|
||||||
logFile: filepath.Join(logDir, "latest.log"),
|
logFile: filepath.Join(logDir, "latest.log"),
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ func TestRsyncProviderWithAuthentication(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
scriptFile := filepath.Join(tmpDir, "myrsync")
|
scriptFile := filepath.Join(tmpDir, "myrsync")
|
||||||
tmpFile := filepath.Join(tmpDir, "log_file")
|
tmpFile := filepath.Join(tmpDir, "log_file")
|
||||||
|
proxyAddr := "127.0.0.1:1233"
|
||||||
|
|
||||||
c := rsyncConfig{
|
c := rsyncConfig{
|
||||||
name: "tuna",
|
name: "tuna",
|
||||||
@@ -123,6 +124,7 @@ func TestRsyncProviderWithAuthentication(t *testing.T) {
|
|||||||
password: "tunasyncpassword",
|
password: "tunasyncpassword",
|
||||||
workingDir: tmpDir,
|
workingDir: tmpDir,
|
||||||
extraOptions: []string{"--delete-excluded"},
|
extraOptions: []string{"--delete-excluded"},
|
||||||
|
rsyncEnv: map[string]string{"RSYNC_PROXY": proxyAddr},
|
||||||
logDir: tmpDir,
|
logDir: tmpDir,
|
||||||
logFile: tmpFile,
|
logFile: tmpFile,
|
||||||
useIPv4: true,
|
useIPv4: true,
|
||||||
@@ -141,7 +143,7 @@ func TestRsyncProviderWithAuthentication(t *testing.T) {
|
|||||||
Convey("Let's try a run", func() {
|
Convey("Let's try a run", func() {
|
||||||
scriptContent := `#!/bin/bash
|
scriptContent := `#!/bin/bash
|
||||||
echo "syncing to $(pwd)"
|
echo "syncing to $(pwd)"
|
||||||
echo $USER $RSYNC_PASSWORD $@
|
echo $USER $RSYNC_PASSWORD $RSYNC_PROXY $@
|
||||||
sleep 1
|
sleep 1
|
||||||
echo "Done"
|
echo "Done"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -156,10 +158,11 @@ exit 0
|
|||||||
"Done\n",
|
"Done\n",
|
||||||
targetDir,
|
targetDir,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%s %s -aHvh --no-o --no-g --stats --exclude .~tmp~/ "+
|
"%s %s %s -aHvh --no-o --no-g --stats --exclude .~tmp~/ "+
|
||||||
"--delete --delete-after --delay-updates --safe-links "+
|
"--delete --delete-after --delay-updates --safe-links "+
|
||||||
"--timeout=120 --contimeout=120 -4 --delete-excluded %s %s",
|
"--timeout=120 --contimeout=120 -4 --delete-excluded %s %s",
|
||||||
provider.username, provider.password, provider.upstreamURL, provider.WorkingDir(),
|
provider.username, provider.password, proxyAddr,
|
||||||
|
provider.upstreamURL, provider.WorkingDir(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -458,14 +461,14 @@ exit 0
|
|||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ --safe-links "+
|
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ --safe-links "+
|
||||||
"--timeout=120 --contimeout=120 --exclude dists/ -6 "+
|
"--timeout=120 --contimeout=120 --exclude dists/ -6 "+
|
||||||
"--exclude-from %s --delete-excluded --cache %s %s",
|
"--exclude-from %s %s %s",
|
||||||
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
||||||
),
|
),
|
||||||
targetDir,
|
targetDir,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ "+
|
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ "+
|
||||||
"--delete --delete-after --delay-updates --safe-links "+
|
"--delete --delete-after --delay-updates --safe-links "+
|
||||||
"--timeout=120 --contimeout=120 -6 --exclude-from %s --delete-excluded --cache %s %s",
|
"--timeout=120 --contimeout=120 --delete-excluded --cache -6 --exclude-from %s %s %s",
|
||||||
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -497,7 +500,7 @@ exit 0
|
|||||||
expectedOutput := fmt.Sprintf(
|
expectedOutput := fmt.Sprintf(
|
||||||
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ --safe-links "+
|
"-aHvh --no-o --no-g --stats --exclude .~tmp~/ --safe-links "+
|
||||||
"--timeout=120 --contimeout=120 --exclude dists/ -6 "+
|
"--timeout=120 --contimeout=120 --exclude dists/ -6 "+
|
||||||
"--exclude-from %s --delete-excluded --cache %s %s\n",
|
"--exclude-from %s %s %s\n",
|
||||||
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
provider.excludeFile, provider.upstreamURL, provider.WorkingDir(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type rsyncConfig struct {
|
|||||||
upstreamURL, username, password, excludeFile string
|
upstreamURL, username, password, excludeFile string
|
||||||
extraOptions []string
|
extraOptions []string
|
||||||
overriddenOptions []string
|
overriddenOptions []string
|
||||||
|
rsyncEnv map[string]string
|
||||||
workingDir, logDir, logFile string
|
workingDir, logDir, logFile string
|
||||||
useIPv6, useIPv4 bool
|
useIPv6, useIPv4 bool
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
@@ -50,6 +51,15 @@ func newRsyncProvider(c rsyncConfig) (*rsyncProvider, error) {
|
|||||||
if c.rsyncCmd == "" {
|
if c.rsyncCmd == "" {
|
||||||
provider.rsyncCmd = "rsync"
|
provider.rsyncCmd = "rsync"
|
||||||
}
|
}
|
||||||
|
if c.rsyncEnv == nil {
|
||||||
|
provider.rsyncEnv = map[string]string{}
|
||||||
|
}
|
||||||
|
if c.username != "" {
|
||||||
|
provider.rsyncEnv["USER"] = c.username
|
||||||
|
}
|
||||||
|
if c.password != "" {
|
||||||
|
provider.rsyncEnv["RSYNC_PASSWORD"] = c.password
|
||||||
|
}
|
||||||
|
|
||||||
options := []string{
|
options := []string{
|
||||||
"-aHvh", "--no-o", "--no-g", "--stats",
|
"-aHvh", "--no-o", "--no-g", "--stats",
|
||||||
@@ -116,18 +126,11 @@ func (p *rsyncProvider) Start() error {
|
|||||||
return errors.New("provider is currently running")
|
return errors.New("provider is currently running")
|
||||||
}
|
}
|
||||||
|
|
||||||
env := map[string]string{}
|
|
||||||
if p.username != "" {
|
|
||||||
env["USER"] = p.username
|
|
||||||
}
|
|
||||||
if p.password != "" {
|
|
||||||
env["RSYNC_PASSWORD"] = p.password
|
|
||||||
}
|
|
||||||
command := []string{p.rsyncCmd}
|
command := []string{p.rsyncCmd}
|
||||||
command = append(command, p.options...)
|
command = append(command, p.options...)
|
||||||
command = append(command, p.upstreamURL, p.WorkingDir())
|
command = append(command, p.upstreamURL, p.WorkingDir())
|
||||||
|
|
||||||
p.cmd = newCmdJob(p, command, p.WorkingDir(), env)
|
p.cmd = newCmdJob(p, command, p.WorkingDir(), p.rsyncEnv)
|
||||||
if err := p.prepareLogFile(false); err != nil {
|
if err := p.prepareLogFile(false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func newCmdJob(provider mirrorProvider, cmdAndArgs []string, workingDir string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmdJob) Start() error {
|
func (c *cmdJob) Start() error {
|
||||||
// logger.Debugf("Command start: %v", c.cmd.Args)
|
logger.Debugf("Command start: %v", c.cmd.Args)
|
||||||
c.finished = make(chan empty, 1)
|
c.finished = make(chan empty, 1)
|
||||||
return c.cmd.Start()
|
return c.cmd.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type twoStageRsyncConfig struct {
|
|||||||
stage1Profile string
|
stage1Profile string
|
||||||
upstreamURL, username, password, excludeFile string
|
upstreamURL, username, password, excludeFile string
|
||||||
extraOptions []string
|
extraOptions []string
|
||||||
|
rsyncEnv map[string]string
|
||||||
workingDir, logDir, logFile string
|
workingDir, logDir, logFile string
|
||||||
useIPv6 bool
|
useIPv6 bool
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
@@ -69,6 +70,15 @@ func newTwoStageRsyncProvider(c twoStageRsyncConfig) (*twoStageRsyncProvider, er
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.rsyncEnv == nil {
|
||||||
|
provider.rsyncEnv = map[string]string{}
|
||||||
|
}
|
||||||
|
if c.username != "" {
|
||||||
|
provider.rsyncEnv["USER"] = c.username
|
||||||
|
}
|
||||||
|
if c.password != "" {
|
||||||
|
provider.rsyncEnv["RSYNC_PASSWORD"] = c.password
|
||||||
|
}
|
||||||
if c.rsyncCmd == "" {
|
if c.rsyncCmd == "" {
|
||||||
provider.rsyncCmd = "rsync"
|
provider.rsyncCmd = "rsync"
|
||||||
}
|
}
|
||||||
@@ -106,6 +116,9 @@ func (p *twoStageRsyncProvider) Options(stage int) ([]string, error) {
|
|||||||
|
|
||||||
} else if stage == 2 {
|
} else if stage == 2 {
|
||||||
options = append(options, p.stage2Options...)
|
options = append(options, p.stage2Options...)
|
||||||
|
if p.extraOptions != nil {
|
||||||
|
options = append(options, p.extraOptions...)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return []string{}, fmt.Errorf("Invalid stage: %d", stage)
|
return []string{}, fmt.Errorf("Invalid stage: %d", stage)
|
||||||
}
|
}
|
||||||
@@ -117,9 +130,6 @@ func (p *twoStageRsyncProvider) Options(stage int) ([]string, error) {
|
|||||||
if p.excludeFile != "" {
|
if p.excludeFile != "" {
|
||||||
options = append(options, "--exclude-from", p.excludeFile)
|
options = append(options, "--exclude-from", p.excludeFile)
|
||||||
}
|
}
|
||||||
if p.extraOptions != nil {
|
|
||||||
options = append(options, p.extraOptions...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
@@ -132,14 +142,6 @@ func (p *twoStageRsyncProvider) Run() error {
|
|||||||
return errors.New("provider is currently running")
|
return errors.New("provider is currently running")
|
||||||
}
|
}
|
||||||
|
|
||||||
env := map[string]string{}
|
|
||||||
if p.username != "" {
|
|
||||||
env["USER"] = p.username
|
|
||||||
}
|
|
||||||
if p.password != "" {
|
|
||||||
env["RSYNC_PASSWORD"] = p.password
|
|
||||||
}
|
|
||||||
|
|
||||||
p.dataSize = ""
|
p.dataSize = ""
|
||||||
stages := []int{1, 2}
|
stages := []int{1, 2}
|
||||||
for _, stage := range stages {
|
for _, stage := range stages {
|
||||||
@@ -151,7 +153,7 @@ func (p *twoStageRsyncProvider) Run() error {
|
|||||||
command = append(command, options...)
|
command = append(command, options...)
|
||||||
command = append(command, p.upstreamURL, p.WorkingDir())
|
command = append(command, p.upstreamURL, p.WorkingDir())
|
||||||
|
|
||||||
p.cmd = newCmdJob(p, command, p.WorkingDir(), env)
|
p.cmd = newCmdJob(p, command, p.WorkingDir(), p.rsyncEnv)
|
||||||
if err := p.prepareLogFile(stage > 1); err != nil {
|
if err := p.prepareLogFile(stage > 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户