package gitserver
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/golang/glog"
"github.com/openshift/origin/pkg/generate/git"
)
var lazyInitMatch = regexp.MustCompile("^/([^\\/]+?)/info/refs$")
// lazyInitRepositoryHandler creates a handler that will initialize a Git repository
// if it does not yet exist.
func lazyInitRepositoryHandler(config *Config, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
handler.ServeHTTP(w, r)
return
}
match := lazyInitMatch.FindStringSubmatch(r.URL.Path)
if match == nil {
handler.ServeHTTP(w, r)
return
}
name := match[1]
if name == "." || name == ".." {
handler.ServeHTTP(w, r)
return
}
if !strings.HasSuffix(name, ".git") {
name += ".git"
}
path := filepath.Join(config.Home, name)
_, err := os.Stat(path)
if !os.IsNotExist(err) {
handler.ServeHTTP(w, r)
return
}
self := RepositoryURL(config, name, r)
log.Printf("Lazily initializing bare repository %s", self.String())
defaultHooks, err := loadHooks(config.HookDirectory)
if err != nil {
log.Printf("error: unable to load default hooks: %v", err)
http.Error(w, fmt.Sprintf("unable to initialize repository: %v", err), http.StatusInternalServerError)
return
}
// TODO: capture init hook output for Git
if _, err := newRepository(config, path, defaultHooks, self, nil); err != nil {
log.Printf("error: unable to initialize repo %s: %v", path, err)
http.Error(w, fmt.Sprintf("unable to initialize repository: %v", err), http.StatusInternalServerError)
os.RemoveAll(path)
return
}
eventCounter.WithLabelValues(name, "init").Inc()
handler.ServeHTTP(w, r)
})
}
// RepositoryURL creates the public URL for the named git repo. If both config.URL and
// request are nil, the returned URL will be nil.
func RepositoryURL(config *Config, name string, r *http.Request) *url.URL {
var url url.URL
switch {
case config.InternalURL != nil:
url = *config.InternalURL
case config.URL != nil:
url = *config.URL
case r != nil:
url = *r.URL
url.Host = r.Host
url.Scheme = "http"
default:
return nil
}
url.Path = "/" + name
url.RawQuery = ""
url.Fragment = ""
return &url
}
func newRepository(config *Config, path string, hooks map[string]string, self *url.URL, origin *url.URL) ([]byte, error) {
var out []byte
repo := git.NewRepositoryForBinary(config.GitBinary)
barePath := path
if !strings.HasSuffix(barePath, ".git") {
barePath += ".git"
}
aliasPath := strings.TrimSuffix(barePath, ".git")
if origin != nil {
if err := repo.CloneMirror(barePath, origin.String()); err != nil {
return out, err
}
} else {
if err := repo.Init(barePath, true); err != nil {
return out, err
}
}
if self != nil {
if err := repo.AddLocalConfig(barePath, "gitserver.self.url", self.String()); err != nil {
return out, err
}
}
// remove all sample hooks, ignore errors here
if files, err := ioutil.ReadDir(filepath.Join(barePath, "hooks")); err == nil {
for _, file := range files {
os.Remove(filepath.Join(barePath, "hooks", file.Name()))
}
}
for name, hook := range hooks {
dest := filepath.Join(barePath, "hooks", name)
if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
return out, err
}
glog.V(5).Infof("Creating hook symlink %s -> %s", dest, hook)
if err := os.Symlink(hook, dest); err != nil {
return out, err
}
}
if initHook, ok := hooks["init"]; ok {
glog.V(5).Infof("Init hook exists, invoking it")
cmd := exec.Command(initHook)
cmd.Dir = barePath
result, err := cmd.CombinedOutput()
glog.V(5).Infof("Init output:\n%s", result)
if err != nil {
return out, fmt.Errorf("init hook failed: %v\n%s", err, string(result))
}
out = result
}
if err := os.Symlink(barePath, aliasPath); err != nil {
return out, fmt.Errorf("cannot create alias path %s: %v", aliasPath, err)
}
return out, nil
}
// clone clones the provided git repositories
func clone(config *Config) error {
defaultHooks, err := loadHooks(config.HookDirectory)
if err != nil {
return err
}
errs := []error{}
for name, v := range config.InitialClones {
hooks := mergeHooks(defaultHooks, v.Hooks)
url := v.URL
url.Fragment = ""
path := filepath.Join(config.Home, name)
ok, err := git.IsBareRoot(path)
if err != nil {
errs = append(errs, err)
continue
}
if ok {
if !config.CleanBeforeClone {
continue
}
log.Printf("Removing %s", path)
if err := os.RemoveAll(path); err != nil {
errs = append(errs, err)
continue
}
}
log.Printf("Cloning %s into %s", url.String(), path)
self := RepositoryURL(config, name, nil)
if _, err := newRepository(config, path, hooks, self, &url); err != nil {
// TODO: tear this directory down
errs = append(errs, err)
continue
}
}
if len(errs) > 0 {
s := []string{}
for _, err := range errs {
s = append(s, err.Error())
}
return fmt.Errorf("initial clone failed:\n* %s", strings.Join(s, "\n* "))
}
return nil
}
func loadHooks(path string) (map[string]string, error) {
glog.V(5).Infof("Loading hooks from directory %s", path)
hooks := make(map[string]string)
if len(path) == 0 {
return hooks, nil
}
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() || (file.Mode().Perm()&0111) == 0 {
continue
}
hook := filepath.Join(path, file.Name())
name := filepath.Base(hook)
glog.V(5).Infof("Adding hook %s at %s", name, hook)
hooks[name] = hook
}
return hooks, nil
}
func mergeHooks(hooks ...map[string]string) map[string]string {
hook := make(map[string]string)
for _, m := range hooks {
for k, v := range m {
hook[k] = v
}
}
return hook
}