devtools/{depotview,hackdoc}: tie both together
Change-Id: I0a1ca3b4fa0e0a074eccbe0f8748839b926db9c1
diff --git a/devtools/hackdoc/main.go b/devtools/hackdoc/main.go
index 938a426..558268b 100644
--- a/devtools/hackdoc/main.go
+++ b/devtools/hackdoc/main.go
@@ -1,24 +1,30 @@
package main
import (
+ "context"
"flag"
"fmt"
"net/http"
"path/filepath"
"regexp"
"strings"
+ "time"
+ "code.hackerspace.pl/hscloud/go/mirko"
+ "code.hackerspace.pl/hscloud/go/pki"
"github.com/golang/glog"
+ "google.golang.org/grpc"
+ dvpb "code.hackerspace.pl/hscloud/devtools/depotview/proto"
"code.hackerspace.pl/hscloud/devtools/hackdoc/config"
"code.hackerspace.pl/hscloud/devtools/hackdoc/source"
)
var (
flagListen = "127.0.0.1:8080"
- flagDocRoot = "./docroot"
+ flagDocRoot = ""
+ flagDepotViewAddress = ""
flagHackdocURL = ""
- flagGitwebURLPattern = "https://gerrit.hackerspace.pl/plugins/gitiles/hscloud/+/%s/%s"
flagGitwebDefaultBranch = "master"
rePagePath = regexp.MustCompile(`^/([A-Za-z0-9_\-/\. ]*)$`)
@@ -29,10 +35,11 @@
}
func main() {
- flag.StringVar(&flagListen, "listen", flagListen, "Address to listen on for HTTP traffic")
- flag.StringVar(&flagDocRoot, "docroot", flagDocRoot, "Path from which to serve documents")
+ flag.StringVar(&flagListen, "pub_listen", flagListen, "Address to listen on for HTTP traffic")
+ flag.StringVar(&flagDocRoot, "docroot", flagDocRoot, "Path from which to serve documents. Either this or depotview must be set")
+ flag.StringVar(&flagDepotViewAddress, "depotview", flagDepotViewAddress, "gRPC endpoint of depotview to serve from Git. Either this or docroot must be set")
flag.StringVar(&flagHackdocURL, "hackdoc_url", flagHackdocURL, "Public URL of hackdoc. If not given, autogenerate from listen path for dev purposes")
- flag.StringVar(&flagGitwebURLPattern, "gitweb_url_pattern", flagGitwebURLPattern, "Pattern to sprintf to for URL for viewing a file in Git. First string is ref/rev, second is bare file path (sans //)")
+ flag.StringVar(&source.FlagGitwebURLPattern, "gitweb_url_pattern", source.FlagGitwebURLPattern, "Pattern to sprintf to for URL for viewing a file in Git. First string is ref/rev, second is bare file path (sans //)")
flag.StringVar(&flagGitwebDefaultBranch, "gitweb_default_rev", flagGitwebDefaultBranch, "Default Git rev to render/link to")
flag.Parse()
@@ -40,25 +47,61 @@
flagHackdocURL = fmt.Sprintf("http://%s", flagListen)
}
- path, err := filepath.Abs(flagDocRoot)
- if err != nil {
- glog.Fatalf("Could not dereference path %q: %w", path, err)
+ if flagDocRoot == "" && flagDepotViewAddress == "" {
+ glog.Errorf("Either -docroot or -depotview must be set")
+ }
+ if flagDocRoot != "" && flagDepotViewAddress != "" {
+ glog.Errorf("Only one of -docroot or -depotview must be set")
}
- s := &service{
- source: source.NewLocal(path),
+ m := mirko.New()
+ if err := m.Listen(); err != nil {
+ glog.Exitf("Listen(): %v", err)
}
- http.HandleFunc("/", s.handler)
+ var s *service
+ if flagDocRoot != "" {
+ path, err := filepath.Abs(flagDocRoot)
+ if err != nil {
+ glog.Exitf("Could not dereference path %q: %w", path, err)
+ }
+ glog.Infof("Starting in docroot mode for %q -> %q", flagDocRoot, path)
+
+ s = &service{
+ source: source.NewSingleRefProvider(source.NewLocal(path)),
+ }
+ } else {
+ glog.Infof("Starting in depotview mode (server %q)", flagDepotViewAddress)
+ conn, err := grpc.Dial(flagDepotViewAddress, pki.WithClientHSPKI())
+ if err != nil {
+ glog.Exitf("grpc.Dial(%q): %v", flagDepotViewAddress, err)
+ }
+ stub := dvpb.NewDepotViewClient(conn)
+ s = &service{
+ source: source.NewDepotView(stub),
+ }
+ }
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", s.handler)
+ srv := &http.Server{Addr: flagListen, Handler: mux}
glog.Infof("Listening on %q...", flagListen)
- if err := http.ListenAndServe(flagListen, nil); err != nil {
- glog.Fatal(err)
- }
+ go func() {
+ if err := srv.ListenAndServe(); err != nil {
+ glog.Error(err)
+ }
+ }()
+
+ <-m.Done()
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ srv.Shutdown(ctx)
+
}
type service struct {
- source source.Source
+ source source.SourceProvider
}
func (s *service) handler(w http.ResponseWriter, r *http.Request) {
@@ -69,23 +112,60 @@
}
glog.Infof("%+v", r.URL.Query())
- rev := r.URL.Query().Get("rev")
- if rev == "" {
- rev = flagGitwebDefaultBranch
+ ref := r.URL.Query().Get("ref")
+ if ref == "" {
+ ref = flagGitwebDefaultBranch
+ }
+
+ ctx := r.Context()
+ source, err := s.source.Source(ctx, ref)
+ switch {
+ case err != nil:
+ glog.Errorf("Source(%q): %v", ref, err)
+ handle500(w, r)
+ return
+ case source == nil:
+ handle404(w, r)
+ return
}
path := r.URL.Path
if match := rePagePath.FindStringSubmatch(path); match != nil {
- s.handlePage(w, r, rev, match[1])
+ req := &request{
+ w: w,
+ r: r,
+ ctx: r.Context(),
+ ref: ref,
+ source: source,
+ }
+ req.handlePage(match[1])
return
}
handle404(w, r)
}
-func logRequest(w http.ResponseWriter, r *http.Request, format string, args ...interface{}) {
- result := fmt.Sprintf(format, args...)
- glog.Infof("result: %s, remote: %q, ua: %q, referrer: %q, host: %q path: %q", result, r.RemoteAddr, r.Header.Get("User-Agent"), r.Header.Get("Referrer"), r.Host, r.URL.Path)
+type request struct {
+ w http.ResponseWriter
+ r *http.Request
+ ctx context.Context
+
+ ref string
+ source source.Source
+ // rpath is the path requested by the client
+ rpath string
+}
+
+func (r *request) handle500() {
+ handle500(r.w, r.r)
+}
+
+func (r *request) handle404() {
+ handle404(r.w, r.r)
+}
+
+func (r *request) logRequest(format string, args ...interface{}) {
+ logRequest(r.w, r.r, format, args...)
}
func urlPathToDepotPath(url string) string {
@@ -109,70 +189,69 @@
return path
}
-func (s *service) handlePageAuto(w http.ResponseWriter, r *http.Request, rev, rpath, dirpath string) {
- cfg, err := config.ForPath(s.source, dirpath)
+func (r *request) handlePageAuto(dirpath string) {
+ cfg, err := config.ForPath(r.ctx, r.source, dirpath)
if err != nil {
glog.Errorf("could not get config for path %q: %w", dirpath, err)
- handle500(w, r)
+ r.handle500()
return
}
for _, f := range cfg.DefaultIndex {
fpath := dirpath + f
- file, err := s.source.IsFile(fpath)
+ file, err := r.source.IsFile(r.ctx, fpath)
if err != nil {
glog.Errorf("IsFile(%q): %w", fpath, err)
- handle500(w, r)
+ r.handle500()
return
}
if file {
- s.handleMarkdown(w, r, s.source, rev, fpath, cfg)
+ r.handleMarkdown(fpath, cfg)
return
}
}
-
- handle404(w, r)
+ r.handle404()
}
-func (s *service) handlePage(w http.ResponseWriter, r *http.Request, rev, page string) {
- path := urlPathToDepotPath(page)
+func (r *request) handlePage(page string) {
+ r.rpath = urlPathToDepotPath(page)
- if strings.HasSuffix(path, "/") {
+ if strings.HasSuffix(r.rpath, "/") {
// Directory path given, autoresolve.
- dirpath := path
- if path != "//" {
- dirpath = strings.TrimSuffix(path, "/") + "/"
+ dirpath := r.rpath
+ if r.rpath != "//" {
+ dirpath = strings.TrimSuffix(r.rpath, "/") + "/"
}
- s.handlePageAuto(w, r, rev, path, dirpath)
+ r.handlePageAuto(dirpath)
return
}
// Otherwise, try loading the file.
- file, err := s.source.IsFile(path)
+ file, err := r.source.IsFile(r.ctx, r.rpath)
if err != nil {
- glog.Errorf("IsFile(%q): %w", path, err)
- handle500(w, r)
+ glog.Errorf("IsFile(%q): %w", r.rpath, err)
+ r.handle500()
return
}
// File exists, render that.
if file {
- parts := strings.Split(path, "/")
+ parts := strings.Split(r.rpath, "/")
dirpath := strings.Join(parts[:(len(parts)-1)], "/")
- cfg, err := config.ForPath(s.source, dirpath)
+ cfg, err := config.ForPath(r.ctx, r.source, dirpath)
if err != nil {
glog.Errorf("could not get config for path %q: %w", dirpath, err)
- handle500(w, r)
+ r.handle500()
return
}
- s.handleMarkdown(w, r, s.source, rev, path, cfg)
+ r.handleMarkdown(r.rpath, cfg)
return
}
// Otherwise assume directory, try all posibilities.
- dirpath := path
- if path != "//" {
- dirpath = strings.TrimSuffix(path, "/") + "/"
+ dirpath := r.rpath
+ if r.rpath != "//" {
+ dirpath = strings.TrimSuffix(r.rpath, "/") + "/"
}
- s.handlePageAuto(w, r, rev, path, dirpath)
+ r.handlePageAuto(dirpath)
}