Add a configurable timeout to the sync process

This commit is contained in:
George Angel 2018-12-30 21:09:15 +00:00
parent 648d31b7f2
commit db07a432cd
1 changed files with 40 additions and 31 deletions

View File

@ -20,6 +20,7 @@ package main // import "k8s.io/git-sync/cmd/git-sync"
import (
"bytes"
"context"
"flag"
"fmt"
"io"
@ -52,6 +53,8 @@ var flDest = flag.String("dest", envString("GIT_SYNC_DEST", ""),
"the name at which to publish the checked-out files under --root (defaults to leaf dir of --repo)")
var flWait = flag.Float64("wait", envFloat("GIT_SYNC_WAIT", 0),
"the number of seconds between syncs")
var flSyncTimeout = flag.Int("timeout", envInt("GIT_SYNC_TIMEOUT", 120),
"the max number of seconds for a complete sync")
var flOneTime = flag.Bool("one-time", envBool("GIT_SYNC_ONE_TIME", false),
"exit after the initial checkout")
var flMaxSyncFailures = flag.Int("max-sync-failures", envInt("GIT_SYNC_MAX_SYNC_FAILURES", 0),
@ -181,7 +184,10 @@ func main() {
initialSync := true
failCount := 0
for {
if err := syncRepo(*flRepo, *flBranch, *flRev, *flDepth, *flRoot, *flDest); err != nil {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(*flSyncTimeout))
defer cancel()
err := syncRepo(ctx, *flRepo, *flBranch, *flRev, *flDepth, *flRoot, *flDest)
if err != nil {
if initialSync || failCount >= *flMaxSyncFailures {
log.Errorf("error syncing repo: %v", err)
os.Exit(1)
@ -197,7 +203,7 @@ func main() {
if *flOneTime {
os.Exit(0)
}
if isHash, err := revIsHash(*flRev, *flRoot); err != nil {
if isHash, err := revIsHash(ctx, *flRev, *flRoot); err != nil {
log.Errorf("can't tell if rev %s is a git hash, exiting", *flRev)
os.Exit(1)
} else if isHash {
@ -237,7 +243,7 @@ func sleepForever() {
}
// updateSymlink atomically swaps the symlink to point at the specified directory and cleans up the previous worktree.
func updateSymlink(gitRoot, link, newDir string) error {
func updateSymlink(ctx context.Context, gitRoot, link, newDir string) error {
// Get currently-linked repo directory (to be removed), unless it doesn't exist
currentDir, err := filepath.EvalSymlinks(path.Join(gitRoot, link))
if err != nil && !os.IsNotExist(err) {
@ -251,12 +257,12 @@ func updateSymlink(gitRoot, link, newDir string) error {
return fmt.Errorf("error converting to relative path: %v", err)
}
if _, err := runCommand(gitRoot, "ln", "-snf", newDirRelative, "tmp-link"); err != nil {
if _, err := runCommand(ctx, gitRoot, "ln", "-snf", newDirRelative, "tmp-link"); err != nil {
return fmt.Errorf("error creating symlink: %v", err)
}
log.V(1).Infof("created symlink %s -> %s", "tmp-link", newDirRelative)
if _, err := runCommand(gitRoot, "mv", "-T", "tmp-link", link); err != nil {
if _, err := runCommand(ctx, gitRoot, "mv", "-T", "tmp-link", link); err != nil {
return fmt.Errorf("error replacing symlink: %v", err)
}
log.V(1).Infof("renamed symlink %s to %s", "tmp-link", link)
@ -269,7 +275,7 @@ func updateSymlink(gitRoot, link, newDir string) error {
log.V(1).Infof("removed %s", currentDir)
_, err := runCommand(gitRoot, "git", "worktree", "prune")
_, err := runCommand(ctx, gitRoot, "git", "worktree", "prune")
if err != nil {
return err
}
@ -281,17 +287,17 @@ func updateSymlink(gitRoot, link, newDir string) error {
}
// addWorktreeAndSwap creates a new worktree and calls updateSymlink to swap the symlink to point to the new worktree
func addWorktreeAndSwap(gitRoot, dest, branch, rev, hash string) error {
func addWorktreeAndSwap(ctx context.Context, gitRoot, dest, branch, rev, hash string) error {
log.V(0).Infof("syncing to %s (%s)", rev, hash)
// Update from the remote.
if _, err := runCommand(gitRoot, "git", "fetch", "--tags", "origin", branch); err != nil {
if _, err := runCommand(ctx, gitRoot, "git", "fetch", "--tags", "origin", branch); err != nil {
return err
}
// Make a worktree for this exact git hash.
worktreePath := path.Join(gitRoot, "rev-"+hash)
_, err := runCommand(gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch)
_, err := runCommand(ctx, gitRoot, "git", "worktree", "add", worktreePath, "origin/"+branch)
if err != nil {
return err
}
@ -311,7 +317,7 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev, hash string) error {
}
// Reset the worktree's working copy to the specific rev.
_, err = runCommand(worktreePath, "git", "reset", "--hard", hash)
_, err = runCommand(ctx, worktreePath, "git", "reset", "--hard", hash)
if err != nil {
return err
}
@ -319,22 +325,22 @@ func addWorktreeAndSwap(gitRoot, dest, branch, rev, hash string) error {
if *flChmod != 0 {
// set file permissions
_, err = runCommand("", "chmod", "-R", strconv.Itoa(*flChmod), worktreePath)
_, err = runCommand(ctx, "", "chmod", "-R", strconv.Itoa(*flChmod), worktreePath)
if err != nil {
return err
}
}
return updateSymlink(gitRoot, dest, worktreePath)
return updateSymlink(ctx, gitRoot, dest, worktreePath)
}
func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error {
func cloneRepo(ctx context.Context, repo, branch, rev string, depth int, gitRoot string) error {
args := []string{"clone", "--no-checkout", "-b", branch}
if depth != 0 {
args = append(args, "--depth", strconv.Itoa(depth))
}
args = append(args, repo, gitRoot)
_, err := runCommand("", "git", args...)
_, err := runCommand(ctx, "", "git", args...)
if err != nil {
if strings.Contains(err.Error(), "already exists and is not an empty directory") {
// Maybe a previous run crashed? Git won't use this dir.
@ -343,7 +349,7 @@ func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error {
if err != nil {
return err
}
_, err = runCommand("", "git", args...)
_, err = runCommand(ctx, "", "git", args...)
if err != nil {
return err
}
@ -356,20 +362,20 @@ func cloneRepo(repo, branch, rev string, depth int, gitRoot string) error {
return nil
}
func hashForRev(rev, gitRoot string) (string, error) {
output, err := runCommand(gitRoot, "git", "rev-parse", rev)
func hashForRev(ctx context.Context, rev, gitRoot string) (string, error) {
output, err := runCommand(ctx, gitRoot, "git", "rev-parse", rev)
if err != nil {
return "", err
}
return strings.Trim(string(output), "\n"), nil
}
func revIsHash(rev, gitRoot string) (bool, error) {
func revIsHash(ctx context.Context, rev, gitRoot string) (bool, error) {
// If rev is a tag name or HEAD, rev-parse will produce the git hash. If
// rev is already a git hash, the output will be the same hash. Of course, a
// user could specify "abc" and match "abcdef12345678", so we just do a
// prefix match.
output, err := hashForRev(rev, gitRoot)
output, err := hashForRev(ctx, rev, gitRoot)
if err != nil {
return false, err
}
@ -377,25 +383,25 @@ func revIsHash(rev, gitRoot string) (bool, error) {
}
// syncRepo syncs the branch of a given repository to the destination at the given rev.
func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error {
func syncRepo(ctx context.Context, repo, branch, rev string, depth int, gitRoot, dest string) error {
target := path.Join(gitRoot, dest)
gitRepoPath := path.Join(target, ".git")
hash := rev
_, err := os.Stat(gitRepoPath)
switch {
case os.IsNotExist(err):
err = cloneRepo(repo, branch, rev, depth, gitRoot)
err = cloneRepo(ctx, repo, branch, rev, depth, gitRoot)
if err != nil {
return err
}
hash, err = hashForRev(rev, gitRoot)
hash, err = hashForRev(ctx, rev, gitRoot)
if err != nil {
return err
}
case err != nil:
return fmt.Errorf("error checking if repo exists %q: %v", gitRepoPath, err)
default:
local, remote, err := getRevs(target, branch, rev)
local, remote, err := getRevs(ctx, target, branch, rev)
if err != nil {
return err
}
@ -410,13 +416,13 @@ func syncRepo(repo, branch, rev string, depth int, gitRoot, dest string) error {
}
}
return addWorktreeAndSwap(gitRoot, dest, branch, rev, hash)
return addWorktreeAndSwap(ctx, gitRoot, dest, branch, rev, hash)
}
// getRevs returns the local and upstream hashes for rev.
func getRevs(localDir, branch, rev string) (string, string, error) {
func getRevs(ctx context.Context, localDir, branch, rev string) (string, string, error) {
// Ask git what the exact hash is for rev.
local, err := hashForRev(rev, localDir)
local, err := hashForRev(ctx, rev, localDir)
if err != nil {
return "", "", err
}
@ -430,7 +436,7 @@ func getRevs(localDir, branch, rev string) (string, string, error) {
}
// Figure out what hash the remote resolves ref to.
remote, err := remoteHashForRef(ref, localDir)
remote, err := remoteHashForRef(ctx, ref, localDir)
if err != nil {
return "", "", err
}
@ -438,8 +444,8 @@ func getRevs(localDir, branch, rev string) (string, string, error) {
return local, remote, nil
}
func remoteHashForRef(ref, gitRoot string) (string, error) {
output, err := runCommand(gitRoot, "git", "ls-remote", "-q", "origin", ref)
func remoteHashForRef(ctx context.Context, ref, gitRoot string) (string, error) {
output, err := runCommand(ctx, gitRoot, "git", "ls-remote", "-q", "origin", ref)
if err != nil {
return "", err
}
@ -459,14 +465,17 @@ func cmdForLog(command string, args ...string) string {
return command + " " + strings.Join(args, " ")
}
func runCommand(cwd, command string, args ...string) (string, error) {
func runCommand(ctx context.Context, cwd, command string, args ...string) (string, error) {
log.V(5).Infof("run(%q): %s", cwd, cmdForLog(command, args...))
cmd := exec.Command(command, args...)
cmd := exec.CommandContext(ctx, command, args...)
if cwd != "" {
cmd.Dir = cwd
}
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("command timed out: %v: %q", err, string(output))
}
if err != nil {
return "", fmt.Errorf("error running command: %v: %q", err, string(output))
}