Move initialization code from main.go to the registry package
This makes it easier to embed a registry instance inside another application. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>master
							parent
							
								
									7a305cc8cd
								
							
						
					
					
						commit
						9b69e40c93
					
				|  | @ -1,28 +1,17 @@ | ||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" |  | ||||||
| 	"crypto/x509" |  | ||||||
| 	_ "expvar" |  | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" |  | ||||||
| 	"net/http" |  | ||||||
| 	_ "net/http/pprof" | 	_ "net/http/pprof" | ||||||
| 	"os" | 	"os" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	log "github.com/Sirupsen/logrus" | 	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/configuration" | ||||||
| 	"github.com/docker/distribution/context" | 	"github.com/docker/distribution/registry" | ||||||
| 	"github.com/docker/distribution/health" |  | ||||||
| 	_ "github.com/docker/distribution/registry/auth/htpasswd" | 	_ "github.com/docker/distribution/registry/auth/htpasswd" | ||||||
| 	_ "github.com/docker/distribution/registry/auth/silly" | 	_ "github.com/docker/distribution/registry/auth/silly" | ||||||
| 	_ "github.com/docker/distribution/registry/auth/token" | 	_ "github.com/docker/distribution/registry/auth/token" | ||||||
| 	"github.com/docker/distribution/registry/handlers" |  | ||||||
| 	"github.com/docker/distribution/registry/listener" |  | ||||||
| 	_ "github.com/docker/distribution/registry/proxy" | 	_ "github.com/docker/distribution/registry/proxy" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/azure" | 	_ "github.com/docker/distribution/registry/storage/driver/azure" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/filesystem" | 	_ "github.com/docker/distribution/registry/storage/driver/filesystem" | ||||||
|  | @ -31,10 +20,8 @@ import ( | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/oss" | 	_ "github.com/docker/distribution/registry/storage/driver/oss" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/s3" | 	_ "github.com/docker/distribution/registry/storage/driver/s3" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/swift" | 	_ "github.com/docker/distribution/registry/storage/driver/swift" | ||||||
| 	"github.com/docker/distribution/uuid" |  | ||||||
| 	"github.com/docker/distribution/version" | 	"github.com/docker/distribution/version" | ||||||
| 	gorhandlers "github.com/gorilla/handlers" | 	"golang.org/x/net/context" | ||||||
| 	"github.com/yvasiyarov/gorelic" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var showVersion bool | var showVersion bool | ||||||
|  | @ -52,99 +39,19 @@ func main() { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ctx := context.Background() |  | ||||||
| 	ctx = context.WithValue(ctx, "version", version.Version) |  | ||||||
| 
 |  | ||||||
| 	config, err := resolveConfiguration() | 	config, err := resolveConfiguration() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fatalf("configuration error: %v", err) | 		fatalf("configuration error: %v", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	ctx, err = configureLogging(ctx, config) | 	registry, err := registry.NewRegistry(context.Background(), config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fatalf("error configuring logger: %v", err) | 		log.Fatalln(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// inject a logger into the uuid library. warns us if there is a problem
 | 	err = registry.Serve() | ||||||
| 	// with uuid generation under low entropy.
 |  | ||||||
| 	uuid.Loggerf = context.GetLogger(ctx).Warnf |  | ||||||
| 
 |  | ||||||
| 	app := handlers.NewApp(ctx, *config) |  | ||||||
| 	app.RegisterHealthChecks() |  | ||||||
| 	handler := configureReporting(app) |  | ||||||
| 	handler = alive("/", handler) |  | ||||||
| 	handler = health.Handler(handler) |  | ||||||
| 	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 { | 	if err != nil { | ||||||
| 		context.GetLogger(app).Fatalln(err) | 		log.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) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -186,148 +93,3 @@ func resolveConfiguration() (*configuration.Configuration, error) { | ||||||
| 
 | 
 | ||||||
| 	return config, nil | 	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) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // 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) |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,2 @@ | ||||||
| // Package registry is a placeholder package for registry interface
 | // Package registry provides the main entrypoints for running a registry.
 | ||||||
| // definitions and utilities.
 |  | ||||||
| package registry | package registry | ||||||
|  |  | ||||||
|  | @ -1038,7 +1038,7 @@ func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv { | ||||||
| func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv { | func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv { | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 
 | 
 | ||||||
| 	app := NewApp(ctx, *config) | 	app := NewApp(ctx, config) | ||||||
| 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | 	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app)) | ||||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix) | 	builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ const defaultCheckInterval = 10 * time.Second | ||||||
| type App struct { | type App struct { | ||||||
| 	context.Context | 	context.Context | ||||||
| 
 | 
 | ||||||
| 	Config configuration.Configuration | 	Config *configuration.Configuration | ||||||
| 
 | 
 | ||||||
| 	router           *mux.Router                 // main application router, configured with dispatchers
 | 	router           *mux.Router                 // main application router, configured with dispatchers
 | ||||||
| 	driver           storagedriver.StorageDriver // driver maintains the app global storage driver instance.
 | 	driver           storagedriver.StorageDriver // driver maintains the app global storage driver instance.
 | ||||||
|  | @ -69,7 +69,7 @@ type App struct { | ||||||
| // NewApp takes a configuration and returns a configured app, ready to serve
 | // NewApp takes a configuration and returns a configured app, ready to serve
 | ||||||
| // requests. The app only implements ServeHTTP and can be wrapped in other
 | // requests. The app only implements ServeHTTP and can be wrapped in other
 | ||||||
| // handlers accordingly.
 | // handlers accordingly.
 | ||||||
| func NewApp(ctx context.Context, configuration configuration.Configuration) *App { | func NewApp(ctx context.Context, configuration *configuration.Configuration) *App { | ||||||
| 	app := &App{ | 	app := &App{ | ||||||
| 		Config:  configuration, | 		Config:  configuration, | ||||||
| 		Context: ctx, | 		Context: ctx, | ||||||
|  | @ -117,10 +117,10 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App | ||||||
| 		panic(err) | 		panic(err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	app.configureSecret(&configuration) | 	app.configureSecret(configuration) | ||||||
| 	app.configureEvents(&configuration) | 	app.configureEvents(configuration) | ||||||
| 	app.configureRedis(&configuration) | 	app.configureRedis(configuration) | ||||||
| 	app.configureLogHook(&configuration) | 	app.configureLogHook(configuration) | ||||||
| 
 | 
 | ||||||
| 	options := []storage.RegistryOption{} | 	options := []storage.RegistryOption{} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ func TestAppDispatcher(t *testing.T) { | ||||||
| 		t.Fatalf("error creating registry: %v", err) | 		t.Fatalf("error creating registry: %v", err) | ||||||
| 	} | 	} | ||||||
| 	app := &App{ | 	app := &App{ | ||||||
| 		Config:   configuration.Configuration{}, | 		Config:   &configuration.Configuration{}, | ||||||
| 		Context:  ctx, | 		Context:  ctx, | ||||||
| 		router:   v2.Router(), | 		router:   v2.Router(), | ||||||
| 		driver:   driver, | 		driver:   driver, | ||||||
|  | @ -164,7 +164,7 @@ func TestNewApp(t *testing.T) { | ||||||
| 	// Mostly, with this test, given a sane configuration, we are simply
 | 	// Mostly, with this test, given a sane configuration, we are simply
 | ||||||
| 	// ensuring that NewApp doesn't panic. We might want to tweak this
 | 	// ensuring that NewApp doesn't panic. We might want to tweak this
 | ||||||
| 	// behavior.
 | 	// behavior.
 | ||||||
| 	app := NewApp(ctx, config) | 	app := NewApp(ctx, &config) | ||||||
| 
 | 
 | ||||||
| 	server := httptest.NewServer(app) | 	server := httptest.NewServer(app) | ||||||
| 	builder, err := v2.NewURLBuilderFromString(server.URL) | 	builder, err := v2.NewURLBuilderFromString(server.URL) | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ func TestFileHealthCheck(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 	defer tmpfile.Close() | 	defer tmpfile.Close() | ||||||
| 
 | 
 | ||||||
| 	config := configuration.Configuration{ | 	config := &configuration.Configuration{ | ||||||
| 		Storage: configuration.Storage{ | 		Storage: configuration.Storage{ | ||||||
| 			"inmemory": configuration.Parameters{}, | 			"inmemory": configuration.Parameters{}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -83,7 +83,7 @@ func TestTCPHealthCheck(t *testing.T) { | ||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
| 
 | 
 | ||||||
| 	config := configuration.Configuration{ | 	config := &configuration.Configuration{ | ||||||
| 		Storage: configuration.Storage{ | 		Storage: configuration.Storage{ | ||||||
| 			"inmemory": configuration.Parameters{}, | 			"inmemory": configuration.Parameters{}, | ||||||
| 		}, | 		}, | ||||||
|  | @ -142,7 +142,7 @@ func TestHTTPHealthCheck(t *testing.T) { | ||||||
| 		} | 		} | ||||||
| 	})) | 	})) | ||||||
| 
 | 
 | ||||||
| 	config := configuration.Configuration{ | 	config := &configuration.Configuration{ | ||||||
| 		Storage: configuration.Storage{ | 		Storage: configuration.Storage{ | ||||||
| 			"inmemory": configuration.Parameters{}, | 			"inmemory": configuration.Parameters{}, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,294 @@ | ||||||
|  | package registry | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	log "github.com/Sirupsen/logrus" | ||||||
|  | 	"github.com/Sirupsen/logrus/formatters/logstash" | ||||||
|  | 	"github.com/bugsnag/bugsnag-go" | ||||||
|  | 	"github.com/docker/distribution/configuration" | ||||||
|  | 	ctxu "github.com/docker/distribution/context" | ||||||
|  | 	"github.com/docker/distribution/health" | ||||||
|  | 	"github.com/docker/distribution/registry/handlers" | ||||||
|  | 	"github.com/docker/distribution/registry/listener" | ||||||
|  | 	"github.com/docker/distribution/uuid" | ||||||
|  | 	"github.com/docker/distribution/version" | ||||||
|  | 	gorhandlers "github.com/gorilla/handlers" | ||||||
|  | 	"github.com/yvasiyarov/gorelic" | ||||||
|  | 	"golang.org/x/net/context" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // A Registry represents a complete instance of the registry.
 | ||||||
|  | type Registry struct { | ||||||
|  | 	config  *configuration.Configuration | ||||||
|  | 	app     *handlers.App | ||||||
|  | 	server  *http.Server | ||||||
|  | 	ln      net.Listener | ||||||
|  | 	debugLn net.Listener | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewRegistry creates a new registry from a context and configuration struct.
 | ||||||
|  | func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) { | ||||||
|  | 	// Note this
 | ||||||
|  | 	ctx = ctxu.WithValue(ctx, "version", version.Version) | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	ctx, err = configureLogging(ctx, config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("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 = ctxu.GetLogger(ctx).Warnf | ||||||
|  | 
 | ||||||
|  | 	app := handlers.NewApp(ctx, config) | ||||||
|  | 	// TODO(aaronl): The global scope of the health checks means NewRegistry
 | ||||||
|  | 	// can only be called once per process.
 | ||||||
|  | 	app.RegisterHealthChecks() | ||||||
|  | 	handler := configureReporting(app) | ||||||
|  | 	handler = alive("/", handler) | ||||||
|  | 	handler = health.Handler(handler) | ||||||
|  | 	handler = panicHandler(handler) | ||||||
|  | 	handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) | ||||||
|  | 
 | ||||||
|  | 	server := &http.Server{ | ||||||
|  | 		Handler: handler, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var debugLn net.Listener | ||||||
|  | 	if config.HTTP.Debug.Addr != "" { | ||||||
|  | 		debugLn, err = listener.NewListener("tcp", config.HTTP.Debug.Addr) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("error listening on debug interface: %v", err) | ||||||
|  | 		} | ||||||
|  | 		log.Infof("debug server listening %v", config.HTTP.Debug.Addr) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	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 { | ||||||
|  | 			return nil, 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 { | ||||||
|  | 					return nil, err | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				if ok := pool.AppendCertsFromPEM(caPem); !ok { | ||||||
|  | 					return nil, fmt.Errorf("Could not add CA to pool") | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			for _, subj := range pool.Subjects() { | ||||||
|  | 				ctxu.GetLogger(app).Debugf("CA Subject: %s", string(subj)) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			tlsConf.ClientAuth = tls.RequireAndVerifyClientCert | ||||||
|  | 			tlsConf.ClientCAs = pool | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ln = tls.NewListener(ln, tlsConf) | ||||||
|  | 		ctxu.GetLogger(app).Infof("listening on %v, tls", ln.Addr()) | ||||||
|  | 	} else { | ||||||
|  | 		ctxu.GetLogger(app).Infof("listening on %v", ln.Addr()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &Registry{ | ||||||
|  | 		app:     app, | ||||||
|  | 		config:  config, | ||||||
|  | 		server:  server, | ||||||
|  | 		ln:      ln, | ||||||
|  | 		debugLn: debugLn, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Serve runs the registry's HTTP server(s).
 | ||||||
|  | func (registry *Registry) Serve() error { | ||||||
|  | 	defer registry.ln.Close() | ||||||
|  | 
 | ||||||
|  | 	errChan := make(chan error) | ||||||
|  | 
 | ||||||
|  | 	if registry.debugLn != nil { | ||||||
|  | 		defer registry.debugLn.Close() | ||||||
|  | 		go func() { | ||||||
|  | 			errChan <- http.Serve(registry.debugLn, nil) | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	go func() { | ||||||
|  | 		errChan <- registry.server.Serve(registry.ln) | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	return <-errChan | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 ctxu.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 = ctxu.WithLogger(ctx, ctxu.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 = ctxu.WithLogger(ctx, ctxu.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 = ctxu.WithValues(ctx, config.Log.Fields) | ||||||
|  | 		ctx = ctxu.WithLogger(ctx, ctxu.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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // 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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue