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 }