313 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			313 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			Go
		
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"crypto/tls"
 | |
| 	"crypto/x509"
 | |
| 	_ "expvar"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	_ "net/http/pprof"
 | |
| 	"os"
 | |
| 	"time"
 | |
| 
 | |
| 	log "github.com/Sirupsen/logrus"
 | |
| 	"github.com/Sirupsen/logrus/formatters/logstash"
 | |
| 	"github.com/bugsnag/bugsnag-go"
 | |
| 	"github.com/docker/distribution/configuration"
 | |
| 	"github.com/docker/distribution/context"
 | |
| 	_ "github.com/docker/distribution/health"
 | |
| 	_ "github.com/docker/distribution/registry/auth/htpasswd"
 | |
| 	_ "github.com/docker/distribution/registry/auth/silly"
 | |
| 	_ "github.com/docker/distribution/registry/auth/token"
 | |
| 	"github.com/docker/distribution/registry/handlers"
 | |
| 	"github.com/docker/distribution/registry/listener"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/azure"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/filesystem"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/inmemory"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/oss"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/s3"
 | |
| 	_ "github.com/docker/distribution/registry/storage/driver/swift"
 | |
| 	"github.com/docker/distribution/uuid"
 | |
| 	"github.com/docker/distribution/version"
 | |
| 	gorhandlers "github.com/gorilla/handlers"
 | |
| 	"github.com/yvasiyarov/gorelic"
 | |
| )
 | |
| 
 | |
| var showVersion bool
 | |
| 
 | |
| func init() {
 | |
| 	flag.BoolVar(&showVersion, "version", false, "show the version and exit")
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	flag.Usage = usage
 | |
| 	flag.Parse()
 | |
| 
 | |
| 	if showVersion {
 | |
| 		version.PrintVersion()
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	ctx := context.Background()
 | |
| 	ctx = context.WithValue(ctx, "version", version.Version)
 | |
| 
 | |
| 	config, err := resolveConfiguration()
 | |
| 	if err != nil {
 | |
| 		fatalf("configuration error: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	ctx, err = configureLogging(ctx, config)
 | |
| 	if err != nil {
 | |
| 		fatalf("error configuring logger: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// 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
 | |
| 
 | |
| 	app := handlers.NewApp(ctx, *config)
 | |
| 	handler := configureReporting(app)
 | |
| 	handler = panicHandler(handler)
 | |
| 	handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
 | |
| 
 | |
| 	if config.HTTP.Debug.Addr != "" {
 | |
| 		go debugServer(config.HTTP.Debug.Addr)
 | |
| 	}
 | |
| 
 | |
| 	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 != "" {
 | |
| 		tlsConf := &tls.Config{
 | |
| 			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,
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
 | |
| 		if err != nil {
 | |
| 			context.GetLogger(app).Fatalln(err)
 | |
| 		}
 | |
| 
 | |
| 		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 {
 | |
| 					context.GetLogger(app).Fatalln(err)
 | |
| 				}
 | |
| 
 | |
| 				if ok := pool.AppendCertsFromPEM(caPem); !ok {
 | |
| 					context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool"))
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			for _, subj := range pool.Subjects() {
 | |
| 				context.GetLogger(app).Debugf("CA Subject: %s", string(subj))
 | |
| 			}
 | |
| 
 | |
| 			tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
 | |
| 			tlsConf.ClientCAs = pool
 | |
| 		}
 | |
| 
 | |
| 		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())
 | |
| 	}
 | |
| 
 | |
| 	if err := server.Serve(ln); err != nil {
 | |
| 		context.GetLogger(app).Fatalln(err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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
 | |
| 	}
 | |
| 
 | |
| 	defer fp.Close()
 | |
| 
 | |
| 	config, err := configuration.Parse(fp)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error parsing %s: %v", configurationPath, err)
 | |
| 	}
 | |
| 
 | |
| 	return config, nil
 | |
| }
 | |
| 
 | |
| func configureReporting(app *handlers.App) http.Handler {
 | |
| 	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
 | |
| 		agent.Verbose = app.Config.Reporting.NewRelic.Verbose
 | |
| 		agent.Run()
 | |
| 
 | |
| 		handler = agent.WrapHTTPHandler(handler)
 | |
| 	}
 | |
| 
 | |
| 	return handler
 | |
| }
 | |
| 
 | |
| // configureLogging prepares the context with a logger using the
 | |
| // configuration.
 | |
| func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) {
 | |
| 	if config.Log.Level == "" && config.Log.Formatter == "" {
 | |
| 		// If no config for logging is set, fallback to deprecated "Loglevel".
 | |
| 		log.SetLevel(logLevel(config.Loglevel))
 | |
| 		ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version"))
 | |
| 		return ctx, nil
 | |
| 	}
 | |
| 
 | |
| 	log.SetLevel(logLevel(config.Log.Level))
 | |
| 
 | |
| 	formatter := config.Log.Formatter
 | |
| 	if formatter == "" {
 | |
| 		formatter = "text" // default formatter
 | |
| 	}
 | |
| 
 | |
| 	switch formatter {
 | |
| 	case "json":
 | |
| 		log.SetFormatter(&log.JSONFormatter{
 | |
| 			TimestampFormat: time.RFC3339Nano,
 | |
| 		})
 | |
| 	case "text":
 | |
| 		log.SetFormatter(&log.TextFormatter{
 | |
| 			TimestampFormat: time.RFC3339Nano,
 | |
| 		})
 | |
| 	case "logstash":
 | |
| 		log.SetFormatter(&logstash.LogstashFormatter{
 | |
| 			TimestampFormat: time.RFC3339Nano,
 | |
| 		})
 | |
| 	default:
 | |
| 		// just let the library use default on empty string.
 | |
| 		if config.Log.Formatter != "" {
 | |
| 			return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if config.Log.Formatter != "" {
 | |
| 		log.Debugf("using %q logging formatter", config.Log.Formatter)
 | |
| 	}
 | |
| 
 | |
| 	// log the application version with messages
 | |
| 	ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version"))
 | |
| 
 | |
| 	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)
 | |
| 		}
 | |
| 
 | |
| 		ctx = context.WithValues(ctx, config.Log.Fields)
 | |
| 		ctx = context.WithLogger(ctx, context.GetLogger(ctx, fields...))
 | |
| 	}
 | |
| 
 | |
| 	return ctx, nil
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 | |
| 
 | |
| // 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)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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)
 | |
| 	})
 | |
| }
 |