9950: Load config from /etc/arvados/arv-git-httpd/config.json.
authorTom Clegg <tom@curoverse.com>
Wed, 21 Sep 2016 01:27:32 +0000 (21:27 -0400)
committerTom Clegg <tom@curoverse.com>
Mon, 26 Sep 2016 17:43:27 +0000 (13:43 -0400)
services/arv-git-httpd/auth_handler.go
services/arv-git-httpd/git_handler.go
services/arv-git-httpd/git_handler_test.go
services/arv-git-httpd/gitolite_test.go
services/arv-git-httpd/integration_test.go
services/arv-git-httpd/main.go
services/arv-git-httpd/server.go
services/arv-git-httpd/usage.go [new file with mode: 0644]

index fccb0c9576864634481a2e69b7237def54b6f0ec..6ba0f387986111c805c3a631dc5666e16f75013b 100644 (file)
@@ -5,6 +5,7 @@ import (
        "net/http"
        "os"
        "strings"
+       "sync"
        "time"
 
        "git.curoverse.com/arvados.git/sdk/go/arvadosclient"
@@ -12,13 +13,20 @@ import (
        "git.curoverse.com/arvados.git/sdk/go/httpserver"
 )
 
-var clientPool = arvadosclient.MakeClientPool()
-
 type authHandler struct {
-       handler http.Handler
+       handler    http.Handler
+       clientPool *arvadosclient.ClientPool
+       setupOnce  sync.Once
+}
+
+func (h *authHandler) setup() {
+       os.Setenv("ARVADOS_API_HOST", theConfig.Client.APIHost)
+       h.clientPool = arvadosclient.MakeClientPool()
 }
 
 func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
+       h.setupOnce.Do(h.setup)
+
        var statusCode int
        var statusText string
        var apiToken string
@@ -68,12 +76,12 @@ func (h *authHandler) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
        repoName = pathParts[0]
        repoName = strings.TrimRight(repoName, "/")
 
-       arv := clientPool.Get()
+       arv := h.clientPool.Get()
        if arv == nil {
-               statusCode, statusText = http.StatusInternalServerError, "connection pool failed: "+clientPool.Err().Error()
+               statusCode, statusText = http.StatusInternalServerError, "connection pool failed: "+h.clientPool.Err().Error()
                return
        }
-       defer clientPool.Put(arv)
+       defer h.clientPool.Put(arv)
 
        // Ask API server whether the repository is readable using
        // this token (by trying to read it!)
index 0312b296fc938da2f4950de7ca9240a5c0550825..f4baa725d1f3669c93281c346d719dc7ab732baf 100644 (file)
@@ -23,7 +23,7 @@ func newGitHandler() http.Handler {
                        Env: []string{
                                "GIT_PROJECT_ROOT=" + theConfig.Root,
                                "GIT_HTTP_EXPORT_ALL=",
-                               "SERVER_ADDR=" + theConfig.Addr,
+                               "SERVER_ADDR=" + theConfig.Listen,
                        },
                        InheritEnv: []string{
                                "PATH",
index 35c2f4884f4f99e2894c5125776edfb0db32895c..d87162dca3aa6f80ac16411c4a138e6286fc40e2 100644 (file)
@@ -37,7 +37,7 @@ func (s *GitHandlerSuite) TestEnvVars(c *check.C) {
        c.Check(body, check.Matches, `(?ms).*^GL_BYPASS_ACCESS_CHECKS=yesplease$.*`)
        c.Check(body, check.Matches, `(?ms).*^REMOTE_HOST=::1$.*`)
        c.Check(body, check.Matches, `(?ms).*^REMOTE_PORT=12345$.*`)
-       c.Check(body, check.Matches, `(?ms).*^SERVER_ADDR=`+regexp.QuoteMeta(theConfig.Addr)+`$.*`)
+       c.Check(body, check.Matches, `(?ms).*^SERVER_ADDR=`+regexp.QuoteMeta(theConfig.Listen)+`$.*`)
 }
 
 func (s *GitHandlerSuite) TestCGIErrorOnSplitHostPortError(c *check.C) {
index 20bdae7ec13a5534ebd4f69248869d4980688fa7..96d9d24562195b99ef0be7dd9ff0fef6a115c0f6 100644 (file)
@@ -6,6 +6,7 @@ import (
        "os/exec"
        "strings"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        check "gopkg.in/check.v1"
 )
 
@@ -41,8 +42,11 @@ func (s *GitoliteSuite) SetUpTest(c *check.C) {
        runGitolite("gitolite", "setup", "--admin", "root")
 
        s.tmpRepoRoot = s.gitoliteHome + "/repositories"
-       s.Config = &config{
-               Addr:       ":0",
+       s.Config = &Config{
+               Client: arvados.Client{
+                       APIHost: os.Getenv("ARVADOS_API_HOST"),
+               },
+               Listen:     ":0",
                GitCommand: "/usr/share/gitolite3/gitolite-shell",
                Root:       s.tmpRepoRoot,
        }
@@ -62,6 +66,10 @@ func (s *GitoliteSuite) TearDownTest(c *check.C) {
        // upgrade to Go 1.4.
        os.Setenv("GITOLITE_HTTP_HOME", "")
        os.Setenv("GL_BYPASS_ACCESS_CHECKS", "")
+       if s.gitoliteHome != "" {
+               err := os.RemoveAll(s.gitoliteHome)
+               c.Check(err, check.Equals, nil)
+       }
        s.IntegrationSuite.TearDownTest(c)
 }
 
index 61d83ff8e85a0da8b6579b8a86ffd8c50d5b2551..a548d6d0096602a92440f3ef609e296041457880 100644 (file)
@@ -8,6 +8,7 @@ import (
        "strings"
        "testing"
 
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
        "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        check "gopkg.in/check.v1"
 )
@@ -23,7 +24,7 @@ type IntegrationSuite struct {
        tmpRepoRoot string
        tmpWorkdir  string
        testServer  *server
-       Config      *config
+       Config      *Config
 }
 
 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
@@ -67,8 +68,11 @@ func (s *IntegrationSuite) SetUpTest(c *check.C) {
        c.Assert(err, check.Equals, nil)
 
        if s.Config == nil {
-               s.Config = &config{
-                       Addr:       ":0",
+               s.Config = &Config{
+                       Client: arvados.Client{
+                               APIHost: os.Getenv("ARVADOS_API_HOST"),
+                       },
+                       Listen:     ":0",
                        GitCommand: "/usr/bin/git",
                        Root:       s.tmpRepoRoot,
                }
index 98695c9a9df806164afc59c128e204cb52547cc9..202e0a2aeaef8e54bb44f827948dead46350134f 100644 (file)
@@ -1,31 +1,67 @@
 package main
 
 import (
+       "encoding/json"
        "flag"
        "log"
        "os"
+       "regexp"
+
+       "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/config"
 )
 
-type config struct {
-       Addr       string
+// Server configuration
+type Config struct {
+       Client     arvados.Client
+       Listen     string
        GitCommand string
        Root       string
 }
 
-var theConfig *config
+var theConfig = defaultConfig()
 
-func init() {
-       theConfig = &config{}
-       flag.StringVar(&theConfig.Addr, "address", "0.0.0.0:80",
-               "Address to listen on, \"host:port\".")
-       flag.StringVar(&theConfig.GitCommand, "git-command", "/usr/bin/git",
-               "Path to git or gitolite-shell executable. Each authenticated request will execute this program with a single argument, \"http-backend\".")
+func defaultConfig() *Config {
        cwd, err := os.Getwd()
        if err != nil {
                log.Fatalln("Getwd():", err)
        }
-       flag.StringVar(&theConfig.Root, "repo-root", cwd,
-               "Path to git repositories.")
+       return &Config{
+               Listen:     ":80",
+               GitCommand: "/usr/bin/git",
+               Root:       cwd,
+       }
+}
+
+func init() {
+       const defaultCfgPath = "/etc/arvados/arv-git-httpd/config.json"
+       const deprecated = " (DEPRECATED -- use config file instead)"
+       flag.StringVar(&theConfig.Listen, "address", theConfig.Listen,
+               "Address to listen on, \"host:port\" or \":port\"."+deprecated)
+       flag.StringVar(&theConfig.GitCommand, "git-command", theConfig.GitCommand,
+               "Path to git or gitolite-shell executable. Each authenticated request will execute this program with a single argument, \"http-backend\"."+deprecated)
+       flag.StringVar(&theConfig.Root, "repo-root", theConfig.Root,
+               "Path to git repositories."+deprecated)
+
+       cfgPath := flag.String("config", defaultCfgPath, "Configuration file `path`.")
+       flag.Usage = usage
+       flag.Parse()
+
+       err := config.LoadFile(theConfig, *cfgPath)
+       if err != nil {
+               h := os.Getenv("ARVADOS_API_HOST")
+               if h == "" || !os.IsNotExist(err) || *cfgPath != defaultCfgPath {
+                       log.Fatal(err)
+               }
+               log.Print("DEPRECATED: No config file found, but ARVADOS_API_HOST environment variable is set. Please use a config file instead.")
+               theConfig.Client.APIHost = h
+               if regexp.MustCompile("^(?i:1|yes|true)$").MatchString(os.Getenv("ARVADOS_API_HOST_INSECURE")) {
+                       theConfig.Client.Insecure = true
+               }
+               if j, err := json.MarshalIndent(theConfig, "", "    "); err == nil {
+                       log.Print("Current configuration:\n", string(j))
+               }
+       }
 
        // MakeArvadosClient returns an error if token is unset (even
        // though we don't need to do anything requiring
@@ -37,7 +73,6 @@ func init() {
 }
 
 func main() {
-       flag.Parse()
        srv := &server{}
        if err := srv.Start(); err != nil {
                log.Fatal(err)
index 40e77a812a6ff4c04a524ca76b819555b9e6e69e..e2311d22e876861ecbe8749b8f552aff2c2c2871 100644 (file)
@@ -12,8 +12,8 @@ type server struct {
 
 func (srv *server) Start() error {
        mux := http.NewServeMux()
-       mux.Handle("/", &authHandler{newGitHandler()})
+       mux.Handle("/", &authHandler{handler: newGitHandler()})
        srv.Handler = mux
-       srv.Addr = theConfig.Addr
+       srv.Addr = theConfig.Listen
        return srv.Server.Start()
 }
diff --git a/services/arv-git-httpd/usage.go b/services/arv-git-httpd/usage.go
new file mode 100644 (file)
index 0000000..649fb10
--- /dev/null
@@ -0,0 +1,62 @@
+package main
+
+import (
+       "encoding/json"
+       "flag"
+       "fmt"
+       "os"
+)
+
+func usage() {
+       c := defaultConfig()
+       c.Client.APIHost = "zzzzz.arvadosapi.com:443"
+       exampleConfigFile, err := json.MarshalIndent(c, "    ", "  ")
+       if err != nil {
+               panic(err)
+       }
+       fmt.Fprintf(os.Stderr, `
+
+arv-git-httpd provides authenticated access to Arvados-hosted git repositories.
+
+See http://doc.arvados.org/install/install-arv-git-httpd.html.
+
+Usage: arv-git-httpd [-config path/to/config.json]
+
+Options:
+`)
+       flag.PrintDefaults()
+       fmt.Fprintf(os.Stderr, `
+Example config file:
+    %s
+
+Client.APIHost:
+
+    Address (or address:port) of the Arvados API endpoint.
+
+Client.AuthToken:
+
+    Unused. Normally empty, or omitted entirely.
+
+Client.Insecure:
+
+    True if your Arvados API endpoint uses an unverifiable SSL/TLS
+    certificate.
+
+Listen:
+
+    Local port to listen on. Can be "address:port" or ":port", where
+    "address" is a host IP address or name and "port" is a port number
+    or name.
+
+GitCommand:
+
+    Path to git or gitolite-shell executable. Each authenticated
+    request will execute this program with the single argument
+    "http-backend".
+
+Root:
+
+    Path to git repositories. Defaults to current working directory.
+
+`, exampleConfigFile)
+}