diff --git a/AUTHORS b/AUTHORS index 12d2230..d32bb2a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ +Ryan Burnette (https://ryanburnette.com) AJ ONeal (https://therootcompany.com) diff --git a/LICENSE b/LICENSE index d90f3f9..b0a9688 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,3 @@ -Copyright 2020 AJ ONeal. All rights reserved. +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/README.md b/README.md index 574c4ca..8747438 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,32 @@ **git-deploy** is an app for handling continuous deployment of static websites. +## Usage + +```bash +echo 'GITHUB_SECRET=xxxxxxx' >> .env +./git-deploy init +./git-deploy run --listen :3000 --serve-path ./overrides --exec ./path/to/script.sh +``` + +To manage `git credentials` +see [The Vanilla DevOps Git Credentials Cheatsheet][1] + +[1]: https://coolaj86.com/articles/vanilla-devops-git-credentials-cheatsheet/ + +## Git Info + +The exec script will receive the parent environment as well as + +```bash +GIT_DEPLOY_JOB_ID=xxxxxx +GIT_REF_NAME=master +GIT_REF_TYPE=branch +GIT_REPO_OWNER=example +GIT_REPO_NAME=example +GIT_CLONE_URL=https://github.com/example/example +``` + ## Build ```bash @@ -18,10 +44,6 @@ go generate -mod=vendor ./... go build -mod=vendor . ``` -```bash -./git-deploy run --listen :3000 --serve-path ./overrides -``` - ## Add Webhooks To add a webhook you'll first need a secret @@ -104,4 +126,8 @@ crypto ## License -Copyright 2020. All rights reserved. +Copyright 2020 The git-deploy Authors + +This Source Code Form is subject to the terms of the Mozilla Public \ +License, v. 2.0. If a copy of the MPL was not distributed with this \ +file, You can obtain one at https://mozilla.org/MPL/2.0/. diff --git a/example.sh b/example.sh new file mode 100644 index 0000000..502163b --- /dev/null +++ b/example.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +for x in $@; do + echo "$x" +done + +my_envs='GIT_REF_NAME +GIT_REF_TYPE +GIT_REPO_OWNER +GIT_REPO_NAME +GIT_CLONE_URL' + +echo 'Doing "work" ...' +sleep 5 + +for x in $my_envs; do + echo "$x=${!x}" +done diff --git a/internal/options/options.go b/internal/options/options.go index 15afdf4..5dad15c 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -11,6 +11,7 @@ type ServerConfig struct { TrustProxy bool Compress bool ServePath string + Exec string } var ServerFlags *flag.FlagSet diff --git a/internal/webhooks/github/github.go b/internal/webhooks/github/github.go index 0e1d07a..f8b97ce 100644 --- a/internal/webhooks/github/github.go +++ b/internal/webhooks/github/github.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "strings" "git.ryanburnette.com/ryanburnette/git-deploy/internal/options" "git.ryanburnette.com/ryanburnette/git-deploy/internal/webhooks" @@ -63,16 +64,33 @@ func InitWebhook(providername string, secret *string, envname string) func() { switch e := event.(type) { case *github.PushEvent: - // this is a commit push, do something with it + var branch string + var tag string ref := e.GetRef() // *e.Ref - branch := ref[len("refs/heads/"):] + parts := strings.Split(ref, "/") + refType := parts[1] + prefixLen := len("refs/") + len(refType) + len("/") + refName := ref[prefixLen:] + switch refType { + case "tags": + refType = "tag" + tag = refName + case "heads": + refType = "branch" + branch = refName + } webhooks.Hook(webhooks.Ref{ - Rev: e.GetAfter(), // *e.After - Ref: ref, - Branch: branch, - Repo: e.GetRepo().GetName(), // *e.Repo.Name - Org: e.GetRepo().GetOrganization(), // *e.Repo.Organization + HTTPSURL: e.GetRepo().GetCloneURL(), + SSHURL: e.GetRepo().GetSSHURL(), + Rev: e.GetAfter(), // *e.After + Ref: ref, + RefType: refType, + RefName: refName, + Branch: branch, + Tag: tag, + Repo: e.GetRepo().GetName(), // *e.Repo.Name + Owner: e.GetRepo().GetOwner().GetLogin(), }) /* case *github.PullRequestEvent: diff --git a/internal/webhooks/webhooks.go b/internal/webhooks/webhooks.go index 3e0e503..30a5147 100644 --- a/internal/webhooks/webhooks.go +++ b/internal/webhooks/webhooks.go @@ -17,9 +17,12 @@ type Ref struct { SSHURL string Rev string Ref string + RefType string // tags, heads + RefName string Branch string + Tag string + Owner string Repo string - Org string } var Providers = make(map[string]func()) diff --git a/main.go b/main.go index 171ab1c..7b234a7 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,13 @@ package main import ( "compress/flate" + "encoding/base64" "flag" "fmt" + "log" "net/http" "os" + "os/exec" "time" "git.ryanburnette.com/ryanburnette/git-deploy/assets" @@ -37,6 +40,14 @@ func ver() { fmt.Printf("%s v%s %s (%s)\n", name, version, commit[:7], date) } +type job struct { + ID string // {HTTPSURL}#{BRANCH} + Cmd *exec.Cmd + Ref webhooks.Ref +} + +var jobs = make(map[string]*job) + var runOpts *options.ServerConfig var runFlags *flag.FlagSet var initFlags *flag.FlagSet @@ -48,7 +59,12 @@ func init() { runFlags.StringVar(&runOpts.Addr, "listen", ":3000", "the address and port on which to listen") runFlags.BoolVar(&runOpts.TrustProxy, "trust-proxy", false, "trust X-Forwarded-For header") runFlags.BoolVar(&runOpts.Compress, "compress", true, "enable compression for text,html,js,css,etc") - runFlags.StringVar(&runOpts.ServePath, "serve-path", "", "path to serve, falls back to built-in web app") + runFlags.StringVar( + &runOpts.ServePath, "serve-path", "", + "path to serve, falls back to built-in web app") + runFlags.StringVar( + &runOpts.Exec, "exec", "", + "path to bash script to run with git info as arguments") } func main() { @@ -80,6 +96,11 @@ func main() { initFlags.Parse(args[2:]) case "run": runFlags.Parse(args[2:]) + if "" == runOpts.Exec { + fmt.Printf("--exec is a required flag") + os.Exit(1) + return + } webhooks.MustRegisterAll() serve() default: @@ -141,9 +162,58 @@ func serve() { for { hook := webhooks.Accept() // TODO os.Exec - fmt.Println(hook.Org) - fmt.Println(hook.Repo) - fmt.Println(hook.Branch) + fmt.Printf("%#v\n", hook) + jobID := base64.URLEncoding.EncodeToString([]byte( + fmt.Sprintf("%s#%s", hook.HTTPSURL, hook.RefName), + )) + + args := []string{ + runOpts.Exec, + jobID, + hook.RefName, + hook.RefType, + hook.Owner, + hook.Repo, + hook.HTTPSURL, + } + cmd := exec.Command("bash", args...) + + env := os.Environ() + envs := []string{ + "GIT_DEPLOY_JOB_ID=" + jobID, + "GIT_REF_NAME=" + hook.RefName, + "GIT_REF_TYPE=" + hook.RefType, + "GIT_REPO_OWNER=" + hook.Owner, + "GIT_REPO_NAME=" + hook.Repo, + "GIT_CLONE_URL=" + hook.HTTPSURL, + } + cmd.Env = append(env, envs...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if _, exists := jobs[jobID]; exists { + // TODO put job in backlog + log.Printf("git-deploy job already started for %s#%s\n", hook.HTTPSURL, hook.RefName) + return + } + + if err := cmd.Start(); nil != err { + log.Printf("git-deploy exec error: %s\n", err) + return + } + + jobs[jobID] = &job{ + ID: jobID, + Cmd: cmd, + Ref: hook, + } + + go func() { + log.Printf("git-deploy job for %s#%s started\n", hook.HTTPSURL, hook.RefName) + cmd.Wait() + delete(jobs, jobID) + log.Printf("git-deploy job for %s#%s finished\n", hook.HTTPSURL, hook.RefName) + }() } }()