2014-12-02 01:36:20 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2015-03-20 15:19:07 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2015-01-28 23:45:25 +00:00
|
|
|
_ "expvar"
|
2014-12-02 01:36:20 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2015-03-20 15:19:07 +00:00
|
|
|
"io/ioutil"
|
2014-12-02 01:36:20 +00:00
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
|
|
|
"os"
|
2015-04-10 20:56:19 +00:00
|
|
|
"time"
|
2014-12-02 01:36:20 +00:00
|
|
|
|
|
|
|
log "github.com/Sirupsen/logrus"
|
2015-03-24 03:13:35 +00:00
|
|
|
"github.com/Sirupsen/logrus/formatters/logstash"
|
2014-12-13 01:49:06 +00:00
|
|
|
"github.com/bugsnag/bugsnag-go"
|
2014-12-24 00:01:38 +00:00
|
|
|
"github.com/docker/distribution/configuration"
|
2015-04-10 01:45:39 +00:00
|
|
|
"github.com/docker/distribution/context"
|
2015-08-06 22:28:11 +00:00
|
|
|
"github.com/docker/distribution/health"
|
2015-06-11 02:40:05 +00:00
|
|
|
_ "github.com/docker/distribution/registry/auth/htpasswd"
|
2015-02-11 02:14:23 +00:00
|
|
|
_ "github.com/docker/distribution/registry/auth/silly"
|
|
|
|
_ "github.com/docker/distribution/registry/auth/token"
|
2015-02-11 01:25:40 +00:00
|
|
|
"github.com/docker/distribution/registry/handlers"
|
2015-05-05 08:25:42 +00:00
|
|
|
"github.com/docker/distribution/registry/listener"
|
2015-07-29 18:12:01 +00:00
|
|
|
_ "github.com/docker/distribution/registry/proxy"
|
2015-06-11 22:30:18 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/azure"
|
2015-02-11 02:14:23 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/filesystem"
|
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
2015-03-03 16:57:52 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront"
|
2015-05-11 15:26:51 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/oss"
|
2015-02-11 02:14:23 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/s3"
|
2015-05-11 16:11:47 +00:00
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/swift"
|
2015-07-30 21:00:28 +00:00
|
|
|
"github.com/docker/distribution/uuid"
|
2015-01-29 23:32:49 +00:00
|
|
|
"github.com/docker/distribution/version"
|
2015-02-11 01:25:40 +00:00
|
|
|
gorhandlers "github.com/gorilla/handlers"
|
2015-02-07 00:19:19 +00:00
|
|
|
"github.com/yvasiyarov/gorelic"
|
2014-12-02 01:36:20 +00:00
|
|
|
)
|
|
|
|
|
2015-01-29 23:32:49 +00:00
|
|
|
var showVersion bool
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
flag.BoolVar(&showVersion, "version", false, "show the version and exit")
|
|
|
|
}
|
|
|
|
|
2014-12-02 01:36:20 +00:00
|
|
|
func main() {
|
|
|
|
flag.Usage = usage
|
|
|
|
flag.Parse()
|
|
|
|
|
2015-01-29 23:32:49 +00:00
|
|
|
if showVersion {
|
2015-01-29 23:53:26 +00:00
|
|
|
version.PrintVersion()
|
2015-01-29 23:32:49 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-02-07 00:19:19 +00:00
|
|
|
ctx := context.Background()
|
2015-04-15 23:37:39 +00:00
|
|
|
ctx = context.WithValue(ctx, "version", version.Version)
|
2015-02-07 00:19:19 +00:00
|
|
|
|
2014-12-02 01:36:20 +00:00
|
|
|
config, err := resolveConfiguration()
|
|
|
|
if err != nil {
|
|
|
|
fatalf("configuration error: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-03-25 00:12:04 +00:00
|
|
|
ctx, err = configureLogging(ctx, config)
|
|
|
|
if err != nil {
|
2015-03-24 03:13:35 +00:00
|
|
|
fatalf("error configuring logger: %v", err)
|
|
|
|
}
|
|
|
|
|
2015-07-30 21:00:28 +00:00
|
|
|
// inject a logger into the uuid library. warns us if there is a problem
|
|
|
|
// with uuid generation under low entropy.
|
|
|
|
uuid.Loggerf = context.GetLogger(ctx).Warnf
|
|
|
|
|
2015-02-11 01:25:40 +00:00
|
|
|
app := handlers.NewApp(ctx, *config)
|
2015-08-06 22:28:11 +00:00
|
|
|
app.RegisterHealthChecks()
|
2014-12-13 01:49:06 +00:00
|
|
|
handler := configureReporting(app)
|
2015-08-13 22:13:42 +00:00
|
|
|
handler = alive("/", handler)
|
2015-08-06 22:28:11 +00:00
|
|
|
handler = health.Handler(handler)
|
2015-08-13 22:13:42 +00:00
|
|
|
handler = panicHandler(handler)
|
2015-02-11 01:25:40 +00:00
|
|
|
handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
|
2014-12-02 01:36:20 +00:00
|
|
|
|
2015-01-28 23:45:25 +00:00
|
|
|
if config.HTTP.Debug.Addr != "" {
|
|
|
|
go debugServer(config.HTTP.Debug.Addr)
|
|
|
|
}
|
|
|
|
|
2015-05-05 08:25:42 +00:00
|
|
|
server := &http.Server{
|
|
|
|
Handler: handler,
|
|
|
|
}
|
|
|
|
|
|
|
|
ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr)
|
|
|
|
if err != nil {
|
|
|
|
context.GetLogger(app).Fatalln(err)
|
|
|
|
}
|
|
|
|
defer ln.Close()
|
|
|
|
|
|
|
|
if config.HTTP.TLS.Certificate != "" {
|
2015-03-20 15:19:07 +00:00
|
|
|
tlsConf := &tls.Config{
|
2015-07-30 16:41:15 +00:00
|
|
|
ClientAuth: tls.NoClientCert,
|
|
|
|
NextProtos: []string{"http/1.1"},
|
|
|
|
Certificates: make([]tls.Certificate, 1),
|
|
|
|
MinVersion: tls.VersionTLS10,
|
|
|
|
PreferServerCipherSuites: true,
|
|
|
|
CipherSuites: []uint16{
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
|
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
|
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
|
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
|
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
|
|
|
},
|
2015-05-05 08:25:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
|
|
|
|
if err != nil {
|
|
|
|
context.GetLogger(app).Fatalln(err)
|
2015-03-20 15:19:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(config.HTTP.TLS.ClientCAs) != 0 {
|
|
|
|
pool := x509.NewCertPool()
|
|
|
|
|
|
|
|
for _, ca := range config.HTTP.TLS.ClientCAs {
|
|
|
|
caPem, err := ioutil.ReadFile(ca)
|
|
|
|
if err != nil {
|
2015-04-10 01:45:39 +00:00
|
|
|
context.GetLogger(app).Fatalln(err)
|
2015-03-20 15:19:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ok := pool.AppendCertsFromPEM(caPem); !ok {
|
2015-04-10 01:45:39 +00:00
|
|
|
context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool"))
|
2015-03-20 15:19:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, subj := range pool.Subjects() {
|
2015-04-10 01:45:39 +00:00
|
|
|
context.GetLogger(app).Debugf("CA Subject: %s", string(subj))
|
2015-03-20 15:19:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
|
|
|
|
tlsConf.ClientCAs = pool
|
|
|
|
}
|
|
|
|
|
2015-05-05 08:25:42 +00:00
|
|
|
ln = tls.NewListener(ln, tlsConf)
|
|
|
|
context.GetLogger(app).Infof("listening on %v, tls", ln.Addr())
|
|
|
|
} else {
|
|
|
|
context.GetLogger(app).Infof("listening on %v", ln.Addr())
|
|
|
|
}
|
2015-03-20 15:19:07 +00:00
|
|
|
|
2015-05-05 08:25:42 +00:00
|
|
|
if err := server.Serve(ln); err != nil {
|
|
|
|
context.GetLogger(app).Fatalln(err)
|
2014-12-02 01:36:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func usage() {
|
|
|
|
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>")
|
|
|
|
flag.PrintDefaults()
|
|
|
|
}
|
|
|
|
|
|
|
|
func fatalf(format string, args ...interface{}) {
|
|
|
|
fmt.Fprintf(os.Stderr, format+"\n", args...)
|
|
|
|
usage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resolveConfiguration() (*configuration.Configuration, error) {
|
|
|
|
var configurationPath string
|
|
|
|
|
|
|
|
if flag.NArg() > 0 {
|
|
|
|
configurationPath = flag.Arg(0)
|
|
|
|
} else if os.Getenv("REGISTRY_CONFIGURATION_PATH") != "" {
|
|
|
|
configurationPath = os.Getenv("REGISTRY_CONFIGURATION_PATH")
|
|
|
|
}
|
|
|
|
|
|
|
|
if configurationPath == "" {
|
|
|
|
return nil, fmt.Errorf("configuration path unspecified")
|
|
|
|
}
|
|
|
|
|
|
|
|
fp, err := os.Open(configurationPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-06-12 02:41:00 +00:00
|
|
|
defer fp.Close()
|
|
|
|
|
2014-12-02 01:36:20 +00:00
|
|
|
config, err := configuration.Parse(fp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error parsing %s: %v", configurationPath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return config, nil
|
|
|
|
}
|
|
|
|
|
2015-02-11 01:25:40 +00:00
|
|
|
func configureReporting(app *handlers.App) http.Handler {
|
2014-12-13 01:49:06 +00:00
|
|
|
var handler http.Handler = app
|
|
|
|
|
|
|
|
if app.Config.Reporting.Bugsnag.APIKey != "" {
|
|
|
|
bugsnagConfig := bugsnag.Configuration{
|
|
|
|
APIKey: app.Config.Reporting.Bugsnag.APIKey,
|
|
|
|
// TODO(brianbland): provide the registry version here
|
|
|
|
// AppVersion: "2.0",
|
|
|
|
}
|
|
|
|
if app.Config.Reporting.Bugsnag.ReleaseStage != "" {
|
|
|
|
bugsnagConfig.ReleaseStage = app.Config.Reporting.Bugsnag.ReleaseStage
|
|
|
|
}
|
|
|
|
if app.Config.Reporting.Bugsnag.Endpoint != "" {
|
|
|
|
bugsnagConfig.Endpoint = app.Config.Reporting.Bugsnag.Endpoint
|
|
|
|
}
|
|
|
|
bugsnag.Configure(bugsnagConfig)
|
|
|
|
|
|
|
|
handler = bugsnag.Handler(handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
if app.Config.Reporting.NewRelic.LicenseKey != "" {
|
|
|
|
agent := gorelic.NewAgent()
|
|
|
|
agent.NewrelicLicense = app.Config.Reporting.NewRelic.LicenseKey
|
|
|
|
if app.Config.Reporting.NewRelic.Name != "" {
|
|
|
|
agent.NewrelicName = app.Config.Reporting.NewRelic.Name
|
|
|
|
}
|
|
|
|
agent.CollectHTTPStat = true
|
2015-04-14 20:16:10 +00:00
|
|
|
agent.Verbose = app.Config.Reporting.NewRelic.Verbose
|
2014-12-13 01:49:06 +00:00
|
|
|
agent.Run()
|
|
|
|
|
|
|
|
handler = agent.WrapHTTPHandler(handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
return handler
|
|
|
|
}
|
2015-01-28 23:45:25 +00:00
|
|
|
|
2015-03-25 00:12:04 +00:00
|
|
|
// configureLogging prepares the context with a logger using the
|
|
|
|
// configuration.
|
|
|
|
func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) {
|
2015-03-24 03:13:35 +00:00
|
|
|
if config.Log.Level == "" && config.Log.Formatter == "" {
|
|
|
|
// If no config for logging is set, fallback to deprecated "Loglevel".
|
|
|
|
log.SetLevel(logLevel(config.Loglevel))
|
2015-04-10 01:45:39 +00:00
|
|
|
ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version"))
|
2015-03-25 00:12:04 +00:00
|
|
|
return ctx, nil
|
2015-03-24 03:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
log.SetLevel(logLevel(config.Log.Level))
|
|
|
|
|
2015-04-10 20:56:19 +00:00
|
|
|
formatter := config.Log.Formatter
|
|
|
|
if formatter == "" {
|
|
|
|
formatter = "text" // default formatter
|
|
|
|
}
|
|
|
|
|
|
|
|
switch formatter {
|
2015-03-24 03:13:35 +00:00
|
|
|
case "json":
|
2015-04-10 20:56:19 +00:00
|
|
|
log.SetFormatter(&log.JSONFormatter{
|
|
|
|
TimestampFormat: time.RFC3339Nano,
|
|
|
|
})
|
2015-03-24 03:13:35 +00:00
|
|
|
case "text":
|
2015-04-10 20:56:19 +00:00
|
|
|
log.SetFormatter(&log.TextFormatter{
|
|
|
|
TimestampFormat: time.RFC3339Nano,
|
|
|
|
})
|
2015-03-24 03:13:35 +00:00
|
|
|
case "logstash":
|
2015-04-10 20:56:19 +00:00
|
|
|
log.SetFormatter(&logstash.LogstashFormatter{
|
|
|
|
TimestampFormat: time.RFC3339Nano,
|
|
|
|
})
|
2015-03-24 03:13:35 +00:00
|
|
|
default:
|
|
|
|
// just let the library use default on empty string.
|
|
|
|
if config.Log.Formatter != "" {
|
2015-03-25 00:12:04 +00:00
|
|
|
return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter)
|
2015-03-24 03:13:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Log.Formatter != "" {
|
|
|
|
log.Debugf("using %q logging formatter", config.Log.Formatter)
|
|
|
|
}
|
|
|
|
|
2015-03-25 00:12:04 +00:00
|
|
|
// log the application version with messages
|
2015-04-15 23:37:39 +00:00
|
|
|
ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version"))
|
2015-03-25 00:12:04 +00:00
|
|
|
|
|
|
|
if len(config.Log.Fields) > 0 {
|
|
|
|
// build up the static fields, if present.
|
|
|
|
var fields []interface{}
|
|
|
|
for k := range config.Log.Fields {
|
|
|
|
fields = append(fields, k)
|
|
|
|
}
|
|
|
|
|
2015-04-10 01:45:39 +00:00
|
|
|
ctx = context.WithValues(ctx, config.Log.Fields)
|
|
|
|
ctx = context.WithLogger(ctx, context.GetLogger(ctx, fields...))
|
2015-03-25 00:12:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ctx, nil
|
2015-03-24 03:13:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func logLevel(level configuration.Loglevel) log.Level {
|
|
|
|
l, err := log.ParseLevel(string(level))
|
|
|
|
if err != nil {
|
|
|
|
l = log.InfoLevel
|
|
|
|
log.Warnf("error parsing level %q: %v, using %q ", level, err, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
2015-01-28 23:45:25 +00:00
|
|
|
// debugServer starts the debug server with pprof, expvar among other
|
|
|
|
// endpoints. The addr should not be exposed externally. For most of these to
|
|
|
|
// work, tls cannot be enabled on the endpoint, so it is generally separate.
|
|
|
|
func debugServer(addr string) {
|
|
|
|
log.Infof("debug server listening %v", addr)
|
|
|
|
if err := http.ListenAndServe(addr, nil); err != nil {
|
|
|
|
log.Fatalf("error listening on debug interface: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2015-06-02 02:52:24 +00:00
|
|
|
|
|
|
|
// panicHandler add a HTTP handler to web app. The handler recover the happening
|
|
|
|
// panic. logrus.Panic transmits panic message to pre-config log hooks, which is
|
|
|
|
// defined in config.yml.
|
|
|
|
func panicHandler(handler http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
log.Panic(fmt.Sprintf("%v", err))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
2015-08-13 22:13:42 +00:00
|
|
|
|
|
|
|
// alive simply wraps the handler with a route that always returns an http 200
|
|
|
|
// response when the path is matched. If the path is not matched, the request
|
|
|
|
// is passed to the provided handler. There is no guarantee of anything but
|
|
|
|
// that the server is up. Wrap with other handlers (such as health.Handler)
|
|
|
|
// for greater affect.
|
|
|
|
func alive(path string, handler http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.URL.Path == path {
|
|
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
handler.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|