diff --git a/README.md b/README.md index 46c3f34..20c6539 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ it re-pulls, it updates the destination directory atomically. In order to do this, it uses a git worktree in a subdirectory of the `--root` and flips a symlink. +git-sync can also be configured to make webhook call upon sucessful git repo syncronisation. The call is made when right after the symlink is updated. ## Usage ``` @@ -32,4 +33,26 @@ docker run -d \ nginx ``` +## Example of webhooks usage +**Webhook config example** +A webhook config must be valid JSON. If ```success``` is not specified in the config, git-sync will not wait for a response. +```json +{ + "url": "http://localhost:9090/-/reload", + "method": "POST|GET", + "success": 200 +} +``` +**Usage** + +``` +docker run -d \ + -v /tmp/git-data:/git \ + registry/git-sync:tag \ + --repo=https://github.com/kubernetes/git-sync + --branch=master + --wait=30 + --webhook='[{"url": "http://localhost:9090/-/reload", "method": "POST", "success": 200}]' + --webhook='[{"url": "http://1.2.3.4:9090/-/reload", "method": "POST"}]' +``` [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/git-sync/README.md?pixel)]() diff --git a/cmd/git-sync/main.go b/cmd/git-sync/main.go index 451ef1f..07fce13 100644 --- a/cmd/git-sync/main.go +++ b/cmd/git-sync/main.go @@ -34,6 +34,7 @@ import ( "strings" "time" "net/http" + "encoding/json" "github.com/thockin/glogr" "github.com/thockin/logr" @@ -62,8 +63,11 @@ var flMaxSyncFailures = flag.Int("max-sync-failures", envInt("GIT_SYNC_MAX_SYNC_ "the number of consecutive failures allowed before aborting (the first pull must succeed, -1 disables aborting for any number of failures after the initial sync)") var flChmod = flag.Int("change-permissions", envInt("GIT_SYNC_PERMISSIONS", 0), "the file permissions to apply to the checked-out files") -var flSymlinkUpdatePostUrl = flag.String("symlink-update-post-url", envString("GIT_SYNC_SYMLINK_UPDATE_POST_URL", ""), - "a command to run when the symlink is updated") + +var flWebhooks = flag.String("webhook", envString("GIT_SYNC_WEBHOOK", ""), + "the JSON formatted array of webhooks to be sent when git is synced") +var flWebhookTimeout = flag.Int("webhook-timeout", envInt("GIT_SYNC_WEBHOOK_TIMEOUT", 60), + "timeout for webhook http/s requests") var flUsername = flag.String("username", envString("GIT_SYNC_USERNAME", ""), "the username to use") @@ -84,6 +88,24 @@ var flCookieFile = flag.Bool("cookie-file", envBool("GIT_COOKIE_FILE", false), var flGitCmd = flag.String("git", envString("GIT_SYNC_GIT", "git"), "the git command to run (subject to PATH search)") +// WebHook structure +type Webhook struct { + // URL for the http/s request + URL string `json:"url"` + // Method for the http/s request + Method string `json:"method"` + // Code to look for when determining if the request was successful. + // If this is not specified, request is sent and forgotten about. + Success int `json:"success"` +} + +// Create an http client that has our timeout by default +var netClient = &http.Client{ + Timeout: time.Duration(time.Second * time.Duration(*flWebhookTimeout) ), +} + +// Webhook collection +var WebhookArray = []Webhook{} var log = newLoggerOrDie() @@ -174,6 +196,13 @@ func main() { } } + if *flWebhooks != "" { + if err := json.Unmarshal([]byte(*flWebhooks), &WebhookArray); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing webhooks JSON: %v\n", err) + os.Exit(1) + } + } + if *flSSH { if err := setupGitSSH(*flSSHKnownHosts); err != nil { fmt.Fprintf(os.Stderr, "ERROR: can't configure SSH: %v\n", err) @@ -188,6 +217,7 @@ func main() { } } + // From here on, output goes through logging. log.V(0).Infof("starting up: %q", os.Args) @@ -229,6 +259,24 @@ func main() { } } +// WebhookCall Do webhook call +func WebHookCall(url string, method string, statusCode int) error { + req, err := http.NewRequest(method, url, nil) + if err != nil { + return err + } + + resp, err := netClient.Do(req) + if err != nil { + return err + } + resp.Body.Close() + if resp.StatusCode != statusCode { + return fmt.Errorf("received response code %q expected %q", resp.StatusCode, statusCode) + } + return nil +} + func waitTime(seconds float64) time.Duration { return time.Duration(int(seconds*1000)) * time.Millisecond } @@ -277,18 +325,18 @@ func updateSymlink(ctx context.Context, gitRoot, link, newDir string) error { } log.V(1).Infof("renamed symlink %s to %s", "tmp-link", link) - // If there is a symlink update callback, call it - if len(*flSymlinkUpdatePostUrl) > 0 { - log.V(1).Infof("sending post request to %s", *flSymlinkUpdatePostUrl) - // Send the post request - req, err := http.NewRequest("POST", *flSymlinkUpdatePostUrl, nil) - if err != nil { - fmt.Errorf("error sending post request (after symlink update): %v", err) - } - c := &http.Client{} - resp, err := c.Do(req) - resp.Body.Close() - } + // // If there is a symlink update callback, call it + // if len(*flSymlinkUpdatePostUrl) > 0 { + // log.V(1).Infof("sending post request to %s", *flSymlinkUpdatePostUrl) + // // Send the post request + // req, err := http.NewRequest("POST", *flSymlinkUpdatePostUrl, nil) + // if err != nil { + // fmt.Errorf("error sending post request (after symlink update): %v", err) + // } + // c := &http.Client{} + // resp, err := c.Do(req) + // resp.Body.Close() + // } // Clean up previous worktree if len(currentDir) > 0 { @@ -306,6 +354,16 @@ func updateSymlink(ctx context.Context, gitRoot, link, newDir string) error { log.V(1).Infof("pruned old worktrees") } + // Calling webhook only after new symlink created - one after another + for _, v := range WebhookArray { + log.V(0).Infof("calling webhook %v\n", v.URL) + if err := WebHookCall(v.URL, v.Method, v.Success); err != nil { + log.Errorf("error calling webhook %v: %v", v.URL, err) + } else { + log.V(0).Infof("calling webhook %v was: OK\n", v.URL) + } + } + return nil }