diff --git a/.prettierignore b/.prettierignore index 019e87e..e35b429 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -html/themes/arc/layouts/**/*.html \ No newline at end of file +vendor/ +html/themes/arc/layouts/**/*.html diff --git a/README.md b/README.md index 137752d..aae9149 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,15 @@ need to update them to suit your build process for your project. These ENVs are set before each script is run: ```bash +GIT_REPO_ID=github.com/my-org/my-project + +GIT_CLONE_URL=https://github.com/my-org/my-project.git + 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 +GIT_REPO_OWNER=my-org +GIT_REPO_NAME=my-project ``` ## API diff --git a/examples/dotenv b/examples/dotenv index 9be728c..ea93d32 100644 --- a/examples/dotenv +++ b/examples/dotenv @@ -7,6 +7,11 @@ PORT=4483 # List promotions in descending order PROMOTIONS="production staging master" +TRUST_PROXY=false + +# These repos will be trusted to run .gitdeploy/deploy.sh +TRUST_REPOS=git.example.com/org/project,git.example.com/org/other-project + # List your various webhook secrets GITHUB_SECRET=xxxxxxxxxxxxxxxxxxxxxx,yyyyyyyyyyyyyyyyyy GITEA_SECRET=xxxxxxxxxxxxxxxxxxxxxx diff --git a/internal/options/options.go b/internal/options/options.go index 5dad15c..06088a2 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -9,6 +9,7 @@ var Server *ServerConfig type ServerConfig struct { Addr string TrustProxy bool + RepoList string Compress bool ServePath string Exec string diff --git a/main.go b/main.go index 807654b..6696715 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "os/exec" + "path/filepath" "strings" "time" @@ -57,6 +58,7 @@ var initFlags *flag.FlagSet var promotions []string var promotionList string var defaultPromotionList = "production,staging,master" +var oldScripts string func init() { runOpts = options.Server @@ -64,12 +66,17 @@ func init() { initFlags = options.InitFlags runFlags.StringVar(&runOpts.Addr, "listen", "", "the address and port on which to listen (default :4483)") runFlags.BoolVar(&runOpts.TrustProxy, "trust-proxy", false, "trust X-Forwarded-For header") + runFlags.StringVar(&runOpts.RepoList, "trust-repos", "", + "run '.gitdeploy/deploy.sh' directly from these repos if no local script is present (example: 'git.example.com/org/repo')") 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.Exec, "exec", "", + &oldScripts, "exec", "", + "old alias for --scripts") + runFlags.StringVar( + &runOpts.Exec, "scripts", "", "path to ./scripts/{deploy.sh,promote.sh,etc}") //"path to bash script to run with git info as arguments") runFlags.StringVar(&promotionList, "promotions", "", @@ -115,21 +122,36 @@ func main() { case "run": _ = runFlags.Parse(args[2:]) if "" == runOpts.Exec { - fmt.Printf("--exec is a required flag") - os.Exit(1) - return + if "" != oldScripts { + fmt.Fprintf(os.Stderr, "--exec is deprecated and will be removed. Please use --scripts instead.\n") + runOpts.Exec = oldScripts + } + } + if "" == runOpts.Exec { + runOpts.Exec = "./scripts" + pathname, _ := filepath.Abs("./scripts") + if info, _ := os.Stat("./scripts/deploy.sh"); nil == info || !info.Mode().IsRegular() { + fmt.Printf( + "%q not found.\nPlease provide --scripts ./scripts/ as a path where \"deploy.sh\" can be found\n", + pathname, + ) + os.Exit(1) + } } if 0 == len(runOpts.Addr) { runOpts.Addr = os.Getenv("LISTEN") } if 0 == len(runOpts.Addr) { - runOpts.Addr = "localhost:" + os.Getenv("PORT") + runOpts.Addr = "localhost:4483" } if 0 == len(runOpts.Addr) { fmt.Printf("--listen <[addr]:port> is a required flag") os.Exit(1) return } + if 0 == len(runOpts.RepoList) { + runOpts.RepoList = os.Getenv("REPO_LIST") + } if 0 == len(promotionList) { promotionList = os.Getenv("PROMOTIONS") } @@ -227,6 +249,50 @@ func serve() { }) }) + r.Get("/repos", func(w http.ResponseWriter, r *http.Request) { + repos := []Repo{} + + for _, id := range strings.Fields( + strings.ReplaceAll(runOpts.RepoList, ",", " "), + ) { + repos = append(repos, Repo{ + ID: id, + CloneURL: fmt.Sprintf("https://%s.git", id), + Promotions: promotions, + }) + } + err := filepath.Walk(runOpts.Exec, func(path string, info os.FileInfo, err error) error { + if nil != err { + fmt.Printf("error walking %q: %v\n", path, err) + return nil + } + fmt.Println("path:", path, "name:", info.Name()) + parts := strings.Split(filepath.ToSlash(path), "/") + if len(parts) < 3 { + return nil + } + path = strings.Join(parts[1:], "/") + if info.Mode().IsRegular() && "deploy.sh" == info.Name() && runOpts.Exec != path { + id := filepath.Dir(path) + repos = append(repos, Repo{ + ID: id, + CloneURL: fmt.Sprintf("https://%s.git", id), + Promotions: promotions, + }) + } + return nil + }) + if nil != err { + http.Error(w, "the scripts directory disappeared", http.StatusInternalServerError) + return + } + b, _ := json.MarshalIndent(ReposResponse{ + Success: true, + Repos: repos, + }, "", " ") + w.Header().Set("Content-Type", "application/json") + w.Write(append(b, '\n')) + }) r.Get("/jobs", func(w http.ResponseWriter, r *http.Request) { // again, possible race condition, but not one that much matters jjobs := []Job{} @@ -504,3 +570,16 @@ func runPromote(hook webhooks.Ref, promoteTo string) { // TODO check for backlog }() } + +// ReposResponse is the successful response to /api/repos +type ReposResponse struct { + Success bool `json:"success"` + Repos []Repo `json:"repos"` +} + +// Repo is one of the elements of /api/repos +type Repo struct { + ID string `json:"id"` + CloneURL string `json:"clone_url"` + Promotions []string `json:"_promotions"` +}