Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add directory ownership and enable ephemeral ports #81

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"//config:go_default_library",
"//server:go_default_library",
"@com_github_abbot_go_http_auth//:go_default_library",
"@com_github_nightlyone_lockfile//:go_default_library",
"@com_github_urfave_cli//:go_default_library",
],
)
Expand Down
32 changes: 22 additions & 10 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ gazelle_dependencies()

go_repository(
name = "com_github_abbot_go_http_auth",
commit = "0ddd408d5d60ea76e320503cc7dd091992dee608",
importpath = "github.com/abbot/go-http-auth",
tag = "v0.4.0",
)

go_repository(
Expand All @@ -51,49 +51,49 @@ go_repository(

go_repository(
name = "com_github_djherbis_atime",
commit = "8e47e0e01d08df8b9f840d74299c8ab70a024a30",
importpath = "github.com/djherbis/atime",
tag = "v1.0.0",
)

go_repository(
name = "org_golang_x_crypto",
commit = "ab813273cd59e1333f7ae7bff5d027d4aadf528c",
commit = "ab813273cd59",
importpath = "golang.org/x/crypto",
)

go_repository(
name = "org_golang_x_net",
commit = "1e491301e022f8f977054da4c2d852decd59571f",
commit = "1e491301e022",
importpath = "golang.org/x/net",
)

go_repository(
name = "com_github_golang_protobuf",
commit = "b4deda0973fb4c70b50d226b1af49f3da59f5265",
importpath = "github.com/golang/protobuf",
tag = "v1.1.0",
)

go_repository(
name = "com_google_cloud_go",
commit = "0fd7230b2a7505833d5f69b75cbd6c9582401479",
importpath = "cloud.google.com/go",
tag = "v0.23.0",
)

go_repository(
name = "in_gopkg_yaml_v2",
commit = "5420a8b6744d3b0345ab293f6fcba19c978f1183",
importpath = "gopkg.in/yaml.v2",
tag = "v2.2.1",
)

go_repository(
name = "org_golang_google_appengine",
commit = "150dc57a1b433e64154302bdc40b6bb8aefa313a",
importpath = "google.golang.org/appengine",
tag = "v1.0.0",
)

go_repository(
name = "org_golang_x_oauth2",
commit = "ec22f46f877b4505e0117eeaab541714644fdd28",
commit = "ec22f46f877b",
importpath = "golang.org/x/oauth2",
)

Expand All @@ -105,6 +105,18 @@ go_repository(

go_repository(
name = "com_github_google_go_cmp",
commit = "3af367b6b30c263d47e8895973edcca9a49cf029",
importpath = "github.com/google/go-cmp",
tag = "v0.2.0",
)

go_repository(
name = "com_github_nightlyone_lockfile",
commit = "0ad87eef1443",
importpath = "github.com/nightlyone/lockfile",
)

go_repository(
name = "in_gopkg_check_v1",
commit = "20d25e280405",
importpath = "gopkg.in/check.v1",
)
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func validateConfig(c *Config) error {
return errors.New("The 'max_size' flag/key must be set to a value > 0")
}

if c.Port == 0 {
if c.Port < 0 {
return errors.New("A valid 'port' flag/key must be specified")
}

Expand Down
11 changes: 6 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
module github.com/buchgr/bazel-remote

require (
cloud.google.com/go v0.23.0
cloud.google.com/go v0.23.0 // indirect
github.com/abbot/go-http-auth v0.4.0
github.com/djherbis/atime v1.0.0
github.com/golang/protobuf v1.1.0
github.com/golang/protobuf v1.1.0 // indirect
github.com/google/go-cmp v0.2.0
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443
github.com/urfave/cli v1.20.0
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59
golang.org/x/net v0.0.0-20180530234432-1e491301e022
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 // indirect
golang.org/x/net v0.0.0-20180530234432-1e491301e022 // indirect
golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b
google.golang.org/appengine v1.0.0
google.golang.org/appengine v1.0.0 // indirect
gopkg.in/yaml.v2 v2.2.1
)
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0E
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
github.com/djherbis/atime v1.0.0 h1:ySLvBAM0EvOGaX7TI4dAM5lWj+RdJUCKtGSEHN8SGBg=
github.com/djherbis/atime v1.0.0/go.mod h1:5W+KBIuTwVGcqjIfaTwt+KSYX1o6uep8dtevevQP/f8=
github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443 h1:+2OJrU8cmOstEoh0uQvYemRGVH1O6xtO2oANUWHFnP0=
github.com/nightlyone/lockfile v0.0.0-20180618180623-0ad87eef1443/go.mod h1:JbxfV1Iifij2yhRjXai0oFrbpxszXHRx1E5RuM26o4Y=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo=
Expand All @@ -14,6 +18,7 @@ golang.org/x/net v0.0.0-20180530234432-1e491301e022 h1:MVYFTUmVD3/+ERcvRRI+P/C2+
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b h1:nCwwlzLoBQhkY/S3CJ2CGAU4pYfR8+5/TPGEHT+p5Nk=
golang.org/x/oauth2 v0.0.0-20180529203656-ec22f46f877b/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
google.golang.org/appengine v1.0.0 h1:dN4LljjBKVChsv0XCSI+zbyzdqrkEwX5LQFUMRSGqOc=
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
Expand Down
118 changes: 114 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"

auth "github.com/abbot/go-http-auth"
Expand All @@ -20,9 +26,46 @@ import (

"github.com/buchgr/bazel-remote/config"
"github.com/buchgr/bazel-remote/server"
"github.com/nightlyone/lockfile"
"github.com/urfave/cli"
)

const bazelRemotePidFile = "bazel-remote.pid"

var signalHandlers []func(os.Signal)
var signalHandlersMutex sync.Mutex

//http server.go doesn't export tcpKeepAliveListener so we have to do the same here
type tcpKeepAliveListener struct {
*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
tc, err := ln.AcceptTCP()
if err != nil {
return nil, err
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}

func init() {
// set up a signal handler to clean up if we are interrupted
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() {
select {
case sig := <-c:
signalHandlersMutex.Lock()
defer signalHandlersMutex.Unlock()
for _, fn := range signalHandlers {
fn(sig)
}
}
}()
}

func main() {
app := cli.NewApp()
app.Description = "A remote build cache for Bazel."
Expand Down Expand Up @@ -118,6 +161,30 @@ func main() {
accessLogger := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.LUTC)
errorLogger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.LUTC)

if err := os.MkdirAll(c.Dir, 0755); err != nil {
return err
}
lockAbsPath, err := filepath.Abs(filepath.Join(c.Dir, bazelRemotePidFile))
if err != nil {
return err
}
pidFileLock, err := lockfile.New(lockAbsPath)
if err != nil {
return err
}
err = pidFileLock.TryLock()
if err != nil {
if err == lockfile.ErrBusy {
pid, _ := ioutil.ReadFile(lockAbsPath)
return fmt.Errorf(
"Already locked by pid %v",
strings.Trim(string(pid), "\n"),
)
}
return fmt.Errorf("Could not lock %v: %v", c.Dir, err)
}
defer pidFileLock.Unlock()

diskCache := disk.New(c.Dir, int64(c.MaxSize)*1024*1024*1024)

var proxyCache cache.Cache
Expand All @@ -142,33 +209,76 @@ func main() {

mux := http.NewServeMux()
httpServer := &http.Server{
Addr: c.Host + ":" + strconv.Itoa(c.Port),
Handler: mux,
}

// graceful shutdown on signal
func() {
signalHandlersMutex.Lock()
defer signalHandlersMutex.Unlock()
signalHandlers = append(signalHandlers, func(sig os.Signal) {
errorLogger.Printf("Shutting down server due to signal: %v", sig)
if err := httpServer.Shutdown(context.Background()); err != nil {
errorLogger.Printf("Failed to shutdown http server: %v", err)
}
})
}()

h := server.NewHTTPCache(proxyCache, accessLogger, errorLogger)
mux.HandleFunc("/status", h.StatusPageHandler)

cacheHandler := h.CacheHandler
if c.HtpasswdFile != "" {
cacheHandler = wrapAuthHandler(cacheHandler, c.HtpasswdFile, c.Host)
}

if c.IdleTimeout > 0 {
cacheHandler = wrapIdleHandler(cacheHandler, c.IdleTimeout, accessLogger, httpServer)
}

mux.HandleFunc("/", cacheHandler)
ln, err := net.Listen("tcp", c.Host+":"+strconv.Itoa(c.Port))
if err != nil {
return err
}
defer ln.Close()

// create a unix domain socket and respond with the port when asked
bazelRemoteSocketPath := filepath.Join(c.Dir, fmt.Sprintf("bazel-remote.%d.sock", os.Getpid()))
sock, err := net.Listen("unix", bazelRemoteSocketPath)
if err != nil {
return err
}
defer os.Remove(bazelRemoteSocketPath)
go handlePortRequest(sock, ln.Addr(), errorLogger)

if len(c.TLSCertFile) > 0 && len(c.TLSKeyFile) > 0 {
return httpServer.ListenAndServeTLS(c.TLSCertFile, c.TLSKeyFile)
return httpServer.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, c.TLSCertFile, c.TLSKeyFile)
}
return httpServer.ListenAndServe()
return httpServer.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

serverErr := app.Run(os.Args)
if serverErr != nil {
log.Fatal("bazel-remote terminated: ", serverErr)
}
}

func handlePortRequest(sock net.Listener, addr net.Addr, errorLogger *log.Logger) {
for {
fd, err := sock.Accept()
if err != nil {
errorLogger.Printf("sock: %v", err)
continue
}
if _, err := fd.Write([]byte(addr.String() + "\n")); err != nil {
errorLogger.Printf("sock write: %v", err)
}
if err := fd.Close(); err != nil {
errorLogger.Printf("sock close: %v", err)
}
}
}

func wrapIdleHandler(handler http.HandlerFunc, idleTimeout time.Duration, accessLogger cache.Logger, httpServer *http.Server) http.HandlerFunc {
lastRequest := time.Now()
ticker := time.NewTicker(time.Second)
Expand Down