Set $GITSYNC_HASH in exechook

This commit is contained in:
Tim Hockin 2022-02-06 16:36:58 -08:00
parent b752a5eedc
commit 76854fc806
4 changed files with 60 additions and 39 deletions

View File

@ -720,7 +720,7 @@ func (git *repoSync) SanityCheck(ctx context.Context) bool {
} }
// Check that this is actually the root of the repo. // Check that this is actually the root of the repo.
if root, err := git.run.Run(ctx, git.root, git.cmd, "rev-parse", "--show-toplevel"); err != nil { if root, err := git.run.Run(ctx, git.root, nil, git.cmd, "rev-parse", "--show-toplevel"); err != nil {
git.log.Error(err, "can't get repo toplevel", "repo", git.root) git.log.Error(err, "can't get repo toplevel", "repo", git.root)
return false return false
} else { } else {
@ -732,7 +732,7 @@ func (git *repoSync) SanityCheck(ctx context.Context) bool {
} }
// Consistency-check the repo. // Consistency-check the repo.
if _, err := git.run.Run(ctx, git.root, git.cmd, "fsck", "--no-progress", "--connectivity-only"); err != nil { if _, err := git.run.Run(ctx, git.root, nil, git.cmd, "fsck", "--no-progress", "--connectivity-only"); err != nil {
git.log.Error(err, "repo sanity check failed", "repo", git.root) git.log.Error(err, "repo sanity check failed", "repo", git.root)
return false return false
} }
@ -848,12 +848,12 @@ func (git *repoSync) UpdateSymlink(ctx context.Context, newDir string) (string,
const tmplink = "tmp-link" const tmplink = "tmp-link"
git.log.V(1).Info("creating tmp symlink", "root", linkDir, "dst", newDirRelative, "src", tmplink) git.log.V(1).Info("creating tmp symlink", "root", linkDir, "dst", newDirRelative, "src", tmplink)
if _, err := git.run.Run(ctx, linkDir, "ln", "-snf", newDirRelative, tmplink); err != nil { if _, err := git.run.Run(ctx, linkDir, nil, "ln", "-snf", newDirRelative, tmplink); err != nil {
return "", fmt.Errorf("error creating symlink: %v", err) return "", fmt.Errorf("error creating symlink: %v", err)
} }
git.log.V(1).Info("renaming symlink", "root", linkDir, "oldName", tmplink, "newName", linkFile) git.log.V(1).Info("renaming symlink", "root", linkDir, "oldName", tmplink, "newName", linkFile)
if _, err := git.run.Run(ctx, linkDir, "mv", "-T", tmplink, linkFile); err != nil { if _, err := git.run.Run(ctx, linkDir, nil, "mv", "-T", tmplink, linkFile); err != nil {
return "", fmt.Errorf("error replacing symlink: %v", err) return "", fmt.Errorf("error replacing symlink: %v", err)
} }
@ -882,7 +882,7 @@ func (git *repoSync) CleanupWorkTree(ctx context.Context, gitRoot, worktree stri
git.log.V(1).Info("removing worktree", "path", worktree) git.log.V(1).Info("removing worktree", "path", worktree)
if err := os.RemoveAll(worktree); err != nil { if err := os.RemoveAll(worktree); err != nil {
return fmt.Errorf("error removing directory: %v", err) return fmt.Errorf("error removing directory: %v", err)
} else if _, err := git.run.Run(ctx, gitRoot, git.cmd, "worktree", "prune"); err != nil { } else if _, err := git.run.Run(ctx, git.root, nil, git.cmd, "worktree", "prune"); err != nil {
return err return err
} }
return nil return nil
@ -903,7 +903,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
args = append(args, "origin", fetch) args = append(args, "origin", fetch)
// Update from the remote. // Update from the remote.
if _, err := git.run.Run(ctx, git.root, git.cmd, args...); err != nil { if _, err := git.run.Run(ctx, git.root, nil, git.cmd, args...); err != nil {
return err return err
} }
@ -926,14 +926,14 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
return err return err
} }
_, err := git.run.Run(ctx, git.root, git.cmd, "worktree", "add", "--detach", worktreePath, hash, "--no-checkout") _, err := git.run.Run(ctx, git.root, nil, git.cmd, "worktree", "add", "--detach", worktreePath, hash, "--no-checkout")
git.log.V(0).Info("adding worktree", "path", worktreePath, "hash", hash) git.log.V(0).Info("adding worktree", "path", worktreePath, "hash", hash)
if err != nil { if err != nil {
return err return err
} }
// GC clone // GC clone
if _, err := git.run.Run(ctx, git.root, git.cmd, "gc", "--prune=all"); err != nil { if _, err := git.run.Run(ctx, git.root, nil, git.cmd, "gc", "--prune=all"); err != nil {
return err return err
} }
@ -986,14 +986,14 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
} }
args := []string{"sparse-checkout", "init"} args := []string{"sparse-checkout", "init"}
_, err = git.run.Run(ctx, worktreePath, git.cmd, args...) _, err = git.run.Run(ctx, worktreePath, nil, git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
} }
// Reset the worktree's working copy to the specific rev. // Reset the worktree's working copy to the specific rev.
_, err = git.run.Run(ctx, worktreePath, git.cmd, "reset", "--hard", hash, "--") _, err = git.run.Run(ctx, worktreePath, nil, git.cmd, "reset", "--hard", hash, "--")
if err != nil { if err != nil {
return err return err
} }
@ -1010,7 +1010,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
if git.depth != 0 { if git.depth != 0 {
submodulesArgs = append(submodulesArgs, "--depth", strconv.Itoa(git.depth)) submodulesArgs = append(submodulesArgs, "--depth", strconv.Itoa(git.depth))
} }
_, err = git.run.Run(ctx, worktreePath, git.cmd, submodulesArgs...) _, err = git.run.Run(ctx, worktreePath, nil, git.cmd, submodulesArgs...)
if err != nil { if err != nil {
return err return err
} }
@ -1020,14 +1020,14 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
if git.chmod != 0 { if git.chmod != 0 {
mode := fmt.Sprintf("%#o", git.chmod) mode := fmt.Sprintf("%#o", git.chmod)
git.log.V(0).Info("changing file permissions", "mode", mode) git.log.V(0).Info("changing file permissions", "mode", mode)
_, err = git.run.Run(ctx, "", "chmod", "-R", mode, worktreePath) _, err = git.run.Run(ctx, "", nil, "chmod", "-R", mode, worktreePath)
if err != nil { if err != nil {
return err return err
} }
} }
// Reset the root's rev (so we can prune and so we can rely on it later). // Reset the root's rev (so we can prune and so we can rely on it later).
_, err = git.run.Run(ctx, git.root, git.cmd, "reset", "--hard", hash, "--") _, err = git.run.Run(ctx, git.root, nil, git.cmd, "reset", "--hard", hash, "--")
if err != nil { if err != nil {
return err return err
} }
@ -1066,7 +1066,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
args = append(args, git.repo, git.root) args = append(args, git.repo, git.root)
git.log.V(0).Info("cloning repo", "origin", git.repo, "path", git.root) git.log.V(0).Info("cloning repo", "origin", git.repo, "path", git.root)
_, err := git.run.Run(ctx, "", git.cmd, args...) _, err := git.run.Run(ctx, "", nil, git.cmd, args...)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "already exists and is not an empty directory") { if strings.Contains(err.Error(), "already exists and is not an empty directory") {
// Maybe a previous run crashed? Git won't use this dir. // Maybe a previous run crashed? Git won't use this dir.
@ -1078,7 +1078,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
_, err = git.run.Run(ctx, "", git.cmd, args...) _, err = git.run.Run(ctx, "", nil, git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
@ -1123,7 +1123,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
} }
args := []string{"sparse-checkout", "init"} args := []string{"sparse-checkout", "init"}
_, err = git.run.Run(ctx, git.root, git.cmd, args...) _, err = git.run.Run(ctx, git.root, nil, git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
@ -1134,7 +1134,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
// LocalHashForRev returns the locally known hash for a given rev. // LocalHashForRev returns the locally known hash for a given rev.
func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, error) { func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, error) {
output, err := git.run.Run(ctx, git.root, git.cmd, "rev-parse", rev) output, err := git.run.Run(ctx, git.root, nil, git.cmd, "rev-parse", rev)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1143,7 +1143,7 @@ func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, e
// RemoteHashForRef returns the upstream hash for a given ref. // RemoteHashForRef returns the upstream hash for a given ref.
func (git *repoSync) RemoteHashForRef(ctx context.Context, ref string) (string, error) { func (git *repoSync) RemoteHashForRef(ctx context.Context, ref string) (string, error) {
output, err := git.run.Run(ctx, git.root, git.cmd, "ls-remote", "-q", "origin", ref) output, err := git.run.Run(ctx, git.root, nil, git.cmd, "ls-remote", "-q", "origin", ref)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1161,7 +1161,7 @@ func (git *repoSync) RevIsHash(ctx context.Context) (bool, error) {
func (git *repoSync) ResolveRef(ctx context.Context, ref string) (string, error) { func (git *repoSync) ResolveRef(ctx context.Context, ref string) (string, error) {
// If git doesn't identify rev as a commit, we're done. // If git doesn't identify rev as a commit, we're done.
output, err := git.run.Run(ctx, git.root, git.cmd, "cat-file", "-t", ref) output, err := git.run.Run(ctx, git.root, nil, git.cmd, "cat-file", "-t", ref)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1258,13 +1258,13 @@ func (git *repoSync) GetRevs(ctx context.Context) (string, string, error) {
func (git *repoSync) SetupAuth(ctx context.Context, username, password string) error { func (git *repoSync) SetupAuth(ctx context.Context, username, password string) error {
git.log.V(1).Info("setting up git credential store") git.log.V(1).Info("setting up git credential store")
_, err := git.run.Run(ctx, "", git.cmd, "config", "--global", "credential.helper", "store") _, err := git.run.Run(ctx, "", nil, git.cmd, "config", "--global", "credential.helper", "store")
if err != nil { if err != nil {
return fmt.Errorf("can't configure git credential helper: %w", err) return fmt.Errorf("can't configure git credential helper: %w", err)
} }
creds := fmt.Sprintf("url=%v\nusername=%v\npassword=%v\n", git.repo, username, password) creds := fmt.Sprintf("url=%v\nusername=%v\npassword=%v\n", git.repo, username, password)
_, err = git.run.RunWithStdin(ctx, "", creds, git.cmd, "credential", "approve") _, err = git.run.RunWithStdin(ctx, "", nil, creds, git.cmd, "credential", "approve")
if err != nil { if err != nil {
return fmt.Errorf("can't configure git credentials: %w", err) return fmt.Errorf("can't configure git credentials: %w", err)
} }
@ -1308,7 +1308,7 @@ func (git *repoSync) SetupCookieFile(ctx context.Context) error {
return fmt.Errorf("can't access git cookiefile: %w", err) return fmt.Errorf("can't access git cookiefile: %w", err)
} }
if _, err = git.run.Run(ctx, "", git.cmd, "config", "--global", "http.cookiefile", pathToCookieFile); err != nil { if _, err = git.run.Run(ctx, "", nil, git.cmd, "config", "--global", "http.cookiefile", pathToCookieFile); err != nil {
return fmt.Errorf("can't configure git cookiefile: %w", err) return fmt.Errorf("can't configure git cookiefile: %w", err)
} }
@ -1384,7 +1384,7 @@ func (git *repoSync) setupExtraGitConfigs(ctx context.Context, configsFlag strin
return fmt.Errorf("can't parse --git-config flag: %v", err) return fmt.Errorf("can't parse --git-config flag: %v", err)
} }
for _, kv := range configs { for _, kv := range configs {
if _, err := git.run.Run(ctx, "", git.cmd, "config", "--global", kv.key, kv.val); err != nil { if _, err := git.run.Run(ctx, "", nil, git.cmd, "config", "--global", kv.key, kv.val); err != nil {
return fmt.Errorf("error configuring additional git configs %q %q: %v", kv.key, kv.val, err) return fmt.Errorf("error configuring additional git configs %q %q: %v", kv.key, kv.val, err)
} }
} }
@ -1613,10 +1613,11 @@ OPTIONS
An optional command to be executed after syncing a new hash of the An optional command to be executed after syncing a new hash of the
remote repository. This command does not take any arguments and remote repository. This command does not take any arguments and
executes with the synced repo as its working directory. The executes with the synced repo as its working directory. The
execution is subject to the overall --sync-timeout flag and will environment variable $GITSYNC_HASH will be set to the git SHA that
extend the effective period between sync attempts. This flag was synced. The execution is subject to the overall --sync-timeout
obsoletes --sync-hook-command, but if sync-hook-command is flag and will extend the effective period between sync attempts.
specified, it will take precedence. This flag obsoletes --sync-hook-command, but if sync-hook-command
is specified, it will take precedence.
--exechook-timeout <duration>, $GIT_SYNC_EXECHOOK_TIMEOUT --exechook-timeout <duration>, $GIT_SYNC_EXECHOOK_TIMEOUT
The timeout for the --exechook-command. (default: 30s) The timeout for the --exechook-command. (default: 30s)
@ -1752,7 +1753,8 @@ OPTIONS
The timeout for the --webhook-url. (default: 1s) The timeout for the --webhook-url. (default: 1s)
--webhook-url <string>, $GIT_SYNC_WEBHOOK_URL --webhook-url <string>, $GIT_SYNC_WEBHOOK_URL
A URL for optional webhook notifications when syncs complete. A URL for optional webhook notifications when syncs complete. The
header 'Gitsync-Hash' will be set to the git SHA that was synced.
EXAMPLE USAGE EXAMPLE USAGE
@ -1791,14 +1793,21 @@ AUTHENTICATION
When --cookie-file (GIT_COOKIE_FILE) is specified, the associated When --cookie-file (GIT_COOKIE_FILE) is specified, the associated
cookies can contain authentication information. cookies can contain authentication information.
WEBHOOKS HOOKS
Webhooks are executed asynchronously from the main git-sync process. If a Webhooks and exechooks are executed asynchronously from the main git-sync
--webhook-url is configured, whenever a new hash is synced a call is sent process. If a --webhook-url or --exechook-command is configured, whenever
using the method defined in --webhook-method. Git-sync will retry this a new hash is synced the hook(s) will be invoked. For exechook, that means
webhook call until it succeeds (based on --webhook-success-status). If the command is exec()'ed, and for webhooks that means an HTTP request is
unsuccessful, git-sync will wait --webhook-backoff (default 3s) before sent using the method defined in --webhook-method. Git-sync will retry
re-attempting the webhook call. both forms of hooks until they succeed (exit code 0 for exechooks, or
--webhook-success-status for webhooks). If unsuccessful, git-sync will
wait --exechook-backoff or --webhook-backoff (as appropriate) before
re-trying the hook.
Hooks are not guaranteed to succeed on every single SHA change. For example,
if a hook fails and a new SHA is synced during the backoff period, the
retried hook will fire for the newest SHA.
` `
func printManPage() { func printManPage() {

View File

@ -38,12 +38,12 @@ func NewRunner(logger *logging.Logger) *Runner {
} }
// Run runs given command // Run runs given command
func (r *Runner) Run(ctx context.Context, cwd, command string, args ...string) (string, error) { func (r *Runner) Run(ctx context.Context, cwd string, env []string, command string, args ...string) (string, error) {
return r.RunWithStdin(ctx, cwd, "", command, args...) return r.RunWithStdin(ctx, cwd, env, "", command, args...)
} }
// RunWithStdin runs given command with stardart input // RunWithStdin runs given command with stardart input
func (r *Runner) RunWithStdin(ctx context.Context, cwd, stdin, command string, args ...string) (string, error) { func (r *Runner) RunWithStdin(ctx context.Context, cwd string, env []string, stdin, command string, args ...string) (string, error) {
cmdStr := cmdForLog(command, args...) cmdStr := cmdForLog(command, args...)
r.logger.V(5).Info("running command", "cwd", cwd, "cmd", cmdStr) r.logger.V(5).Info("running command", "cwd", cwd, "cmd", cmdStr)
@ -51,6 +51,9 @@ func (r *Runner) RunWithStdin(ctx context.Context, cwd, stdin, command string, a
if cwd != "" { if cwd != "" {
cmd.Dir = cwd cmd.Dir = cwd
} }
if len(env) != 0 {
cmd.Env = env
}
outbuf := bytes.NewBuffer(nil) outbuf := bytes.NewBuffer(nil)
errbuf := bytes.NewBuffer(nil) errbuf := bytes.NewBuffer(nil)
cmd.Stdout = outbuf cmd.Stdout = outbuf

View File

@ -18,6 +18,7 @@ package hook
import ( import (
"context" "context"
"fmt"
"path/filepath" "path/filepath"
"time" "time"
@ -66,6 +67,10 @@ func (h *Exechook) Do(ctx context.Context, hash string) error {
worktreePath := filepath.Join(h.gitRoot, hash) worktreePath := filepath.Join(h.gitRoot, hash)
h.logger.V(0).Info("running exechook", "command", h.command, "timeout", h.timeout) h.logger.V(0).Info("running exechook", "command", h.command, "timeout", h.timeout)
_, err := h.cmdrunner.Run(ctx, worktreePath, h.command, h.args...) _, err := h.cmdrunner.Run(ctx, worktreePath, []string{envKV("GITSYNC_HASH", hash)}, h.command, h.args...)
return err return err
} }
func envKV(k, v string) string {
return fmt.Sprintf("%s=%s", k, v)
}

View File

@ -17,5 +17,9 @@
# Use for e2e test of --exechook-command. # Use for e2e test of --exechook-command.
# This option takes no command arguments, so requires a wrapper script. # This option takes no command arguments, so requires a wrapper script.
if [ -z "${GITSYNC_HASH}" ]; then
echo "GITSYNC_HASH is not set" > exechook
exit 1
fi
cat file > exechook cat file > exechook
cat ../link/file > link-exechook cat ../link/file > link-exechook