github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/handlers/app.go (about)

     1  package handlers
     2  
     3  import (
     4  	cryptorand "crypto/rand"
     5  	"expvar"
     6  	"fmt"
     7  	"math/rand"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"time"
    13  
    14  	log "github.com/Sirupsen/logrus"
    15  	"github.com/docker/distribution"
    16  	"github.com/docker/distribution/configuration"
    17  	ctxu "github.com/docker/distribution/context"
    18  	"github.com/docker/distribution/health"
    19  	"github.com/docker/distribution/health/checks"
    20  	"github.com/docker/distribution/notifications"
    21  	"github.com/docker/distribution/registry/api/errcode"
    22  	"github.com/docker/distribution/registry/api/v2"
    23  	"github.com/docker/distribution/registry/auth"
    24  	registrymiddleware "github.com/docker/distribution/registry/middleware/registry"
    25  	repositorymiddleware "github.com/docker/distribution/registry/middleware/repository"
    26  	"github.com/docker/distribution/registry/proxy"
    27  	"github.com/docker/distribution/registry/storage"
    28  	memorycache "github.com/docker/distribution/registry/storage/cache/memory"
    29  	rediscache "github.com/docker/distribution/registry/storage/cache/redis"
    30  	storagedriver "github.com/docker/distribution/registry/storage/driver"
    31  	"github.com/docker/distribution/registry/storage/driver/factory"
    32  	storagemiddleware "github.com/docker/distribution/registry/storage/driver/middleware"
    33  	"github.com/docker/libtrust"
    34  	"github.com/garyburd/redigo/redis"
    35  	"github.com/gorilla/mux"
    36  	"golang.org/x/net/context"
    37  )
    38  
    39  // randomSecretSize is the number of random bytes to generate if no secret
    40  // was specified.
    41  const randomSecretSize = 32
    42  
    43  // defaultCheckInterval is the default time in between health checks
    44  const defaultCheckInterval = 10 * time.Second
    45  
    46  // App is a global registry application object. Shared resources can be placed
    47  // on this object that will be accessible from all requests. Any writable
    48  // fields should be protected.
    49  type App struct {
    50  	context.Context
    51  
    52  	Config *configuration.Configuration
    53  
    54  	router           *mux.Router                 // main application router, configured with dispatchers
    55  	driver           storagedriver.StorageDriver // driver maintains the app global storage driver instance.
    56  	registry         distribution.Namespace      // registry is the primary registry backend for the app instance.
    57  	accessController auth.AccessController       // main access controller for application
    58  
    59  	// httpHost is a parsed representation of the http.host parameter from
    60  	// the configuration. Only the Scheme and Host fields are used.
    61  	httpHost url.URL
    62  
    63  	// events contains notification related configuration.
    64  	events struct {
    65  		sink   notifications.Sink
    66  		source notifications.SourceRecord
    67  	}
    68  
    69  	redis *redis.Pool
    70  
    71  	// trustKey is a deprecated key used to sign manifests converted to
    72  	// schema1 for backward compatibility. It should not be used for any
    73  	// other purposes.
    74  	trustKey libtrust.PrivateKey
    75  
    76  	// isCache is true if this registry is configured as a pull through cache
    77  	isCache bool
    78  
    79  	// readOnly is true if the registry is in a read-only maintenance mode
    80  	readOnly bool
    81  }
    82  
    83  // NewApp takes a configuration and returns a configured app, ready to serve
    84  // requests. The app only implements ServeHTTP and can be wrapped in other
    85  // handlers accordingly.
    86  func NewApp(ctx context.Context, configuration *configuration.Configuration) *App {
    87  	app := &App{
    88  		Config:  configuration,
    89  		Context: ctx,
    90  		router:  v2.RouterWithPrefix(configuration.HTTP.Prefix),
    91  		isCache: configuration.Proxy.RemoteURL != "",
    92  	}
    93  
    94  	// Register the handler dispatchers.
    95  	app.register(v2.RouteNameBase, func(ctx *Context, r *http.Request) http.Handler {
    96  		return http.HandlerFunc(apiBase)
    97  	})
    98  	app.register(v2.RouteNameManifest, imageManifestDispatcher)
    99  	app.register(v2.RouteNameCatalog, catalogDispatcher)
   100  	app.register(v2.RouteNameTags, tagsDispatcher)
   101  	app.register(v2.RouteNameBlob, blobDispatcher)
   102  	app.register(v2.RouteNameBlobUpload, blobUploadDispatcher)
   103  	app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher)
   104  
   105  	var err error
   106  	app.driver, err = factory.Create(configuration.Storage.Type(), configuration.Storage.Parameters())
   107  	if err != nil {
   108  		// TODO(stevvooe): Move the creation of a service into a protected
   109  		// method, where this is created lazily. Its status can be queried via
   110  		// a health check.
   111  		panic(err)
   112  	}
   113  
   114  	purgeConfig := uploadPurgeDefaultConfig()
   115  	if mc, ok := configuration.Storage["maintenance"]; ok {
   116  		if v, ok := mc["uploadpurging"]; ok {
   117  			purgeConfig, ok = v.(map[interface{}]interface{})
   118  			if !ok {
   119  				panic("uploadpurging config key must contain additional keys")
   120  			}
   121  		}
   122  		if v, ok := mc["readonly"]; ok {
   123  			readOnly, ok := v.(map[interface{}]interface{})
   124  			if !ok {
   125  				panic("readonly config key must contain additional keys")
   126  			}
   127  			if readOnlyEnabled, ok := readOnly["enabled"]; ok {
   128  				app.readOnly, ok = readOnlyEnabled.(bool)
   129  				if !ok {
   130  					panic("readonly's enabled config key must have a boolean value")
   131  				}
   132  			}
   133  		}
   134  	}
   135  
   136  	startUploadPurger(app, app.driver, ctxu.GetLogger(app), purgeConfig)
   137  
   138  	app.driver, err = applyStorageMiddleware(app.driver, configuration.Middleware["storage"])
   139  	if err != nil {
   140  		panic(err)
   141  	}
   142  
   143  	app.configureSecret(configuration)
   144  	app.configureEvents(configuration)
   145  	app.configureRedis(configuration)
   146  	app.configureLogHook(configuration)
   147  
   148  	// Generate an ephemeral key to be used for signing converted manifests
   149  	// for clients that don't support schema2.
   150  	app.trustKey, err = libtrust.GenerateECP256PrivateKey()
   151  	if err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	if configuration.HTTP.Host != "" {
   156  		u, err := url.Parse(configuration.HTTP.Host)
   157  		if err != nil {
   158  			panic(fmt.Sprintf(`could not parse http "host" parameter: %v`, err))
   159  		}
   160  		app.httpHost = *u
   161  	}
   162  
   163  	options := []storage.RegistryOption{}
   164  
   165  	if app.isCache {
   166  		options = append(options, storage.DisableDigestResumption)
   167  	}
   168  
   169  	// configure deletion
   170  	if d, ok := configuration.Storage["delete"]; ok {
   171  		e, ok := d["enabled"]
   172  		if ok {
   173  			if deleteEnabled, ok := e.(bool); ok && deleteEnabled {
   174  				options = append(options, storage.EnableDelete)
   175  			}
   176  		}
   177  	}
   178  
   179  	// configure redirects
   180  	var redirectDisabled bool
   181  	if redirectConfig, ok := configuration.Storage["redirect"]; ok {
   182  		v := redirectConfig["disable"]
   183  		switch v := v.(type) {
   184  		case bool:
   185  			redirectDisabled = v
   186  		default:
   187  			panic(fmt.Sprintf("invalid type for redirect config: %#v", redirectConfig))
   188  		}
   189  	}
   190  	if redirectDisabled {
   191  		ctxu.GetLogger(app).Infof("backend redirection disabled")
   192  	} else {
   193  		options = append(options, storage.EnableRedirect)
   194  	}
   195  
   196  	// configure storage caches
   197  	if cc, ok := configuration.Storage["cache"]; ok {
   198  		v, ok := cc["blobdescriptor"]
   199  		if !ok {
   200  			// Backwards compatible: "layerinfo" == "blobdescriptor"
   201  			v = cc["layerinfo"]
   202  		}
   203  
   204  		switch v {
   205  		case "redis":
   206  			if app.redis == nil {
   207  				panic("redis configuration required to use for layerinfo cache")
   208  			}
   209  			cacheProvider := rediscache.NewRedisBlobDescriptorCacheProvider(app.redis)
   210  			localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
   211  			app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
   212  			if err != nil {
   213  				panic("could not create registry: " + err.Error())
   214  			}
   215  			ctxu.GetLogger(app).Infof("using redis blob descriptor cache")
   216  		case "inmemory":
   217  			cacheProvider := memorycache.NewInMemoryBlobDescriptorCacheProvider()
   218  			localOptions := append(options, storage.BlobDescriptorCacheProvider(cacheProvider))
   219  			app.registry, err = storage.NewRegistry(app, app.driver, localOptions...)
   220  			if err != nil {
   221  				panic("could not create registry: " + err.Error())
   222  			}
   223  			ctxu.GetLogger(app).Infof("using inmemory blob descriptor cache")
   224  		default:
   225  			if v != "" {
   226  				ctxu.GetLogger(app).Warnf("unknown cache type %q, caching disabled", configuration.Storage["cache"])
   227  			}
   228  		}
   229  	}
   230  
   231  	if app.registry == nil {
   232  		// configure the registry if no cache section is available.
   233  		app.registry, err = storage.NewRegistry(app.Context, app.driver, options...)
   234  		if err != nil {
   235  			panic("could not create registry: " + err.Error())
   236  		}
   237  	}
   238  
   239  	app.registry, err = applyRegistryMiddleware(app.Context, app.registry, configuration.Middleware["registry"])
   240  	if err != nil {
   241  		panic(err)
   242  	}
   243  
   244  	authType := configuration.Auth.Type()
   245  
   246  	if authType != "" {
   247  		accessController, err := auth.GetAccessController(configuration.Auth.Type(), configuration.Auth.Parameters())
   248  		if err != nil {
   249  			panic(fmt.Sprintf("unable to configure authorization (%s): %v", authType, err))
   250  		}
   251  		app.accessController = accessController
   252  		ctxu.GetLogger(app).Debugf("configured %q access controller", authType)
   253  	}
   254  
   255  	// configure as a pull through cache
   256  	if configuration.Proxy.RemoteURL != "" {
   257  		app.registry, err = proxy.NewRegistryPullThroughCache(ctx, app.registry, app.driver, configuration.Proxy)
   258  		if err != nil {
   259  			panic(err.Error())
   260  		}
   261  		app.isCache = true
   262  		ctxu.GetLogger(app).Info("Registry configured as a proxy cache to ", configuration.Proxy.RemoteURL)
   263  	}
   264  
   265  	return app
   266  }
   267  
   268  // RegisterHealthChecks is an awful hack to defer health check registration
   269  // control to callers. This should only ever be called once per registry
   270  // process, typically in a main function. The correct way would be register
   271  // health checks outside of app, since multiple apps may exist in the same
   272  // process. Because the configuration and app are tightly coupled,
   273  // implementing this properly will require a refactor. This method may panic
   274  // if called twice in the same process.
   275  func (app *App) RegisterHealthChecks(healthRegistries ...*health.Registry) {
   276  	if len(healthRegistries) > 1 {
   277  		panic("RegisterHealthChecks called with more than one registry")
   278  	}
   279  	healthRegistry := health.DefaultRegistry
   280  	if len(healthRegistries) == 1 {
   281  		healthRegistry = healthRegistries[0]
   282  	}
   283  
   284  	if app.Config.Health.StorageDriver.Enabled {
   285  		interval := app.Config.Health.StorageDriver.Interval
   286  		if interval == 0 {
   287  			interval = defaultCheckInterval
   288  		}
   289  
   290  		storageDriverCheck := func() error {
   291  			_, err := app.driver.List(app, "/") // "/" should always exist
   292  			return err                          // any error will be treated as failure
   293  		}
   294  
   295  		if app.Config.Health.StorageDriver.Threshold != 0 {
   296  			healthRegistry.RegisterPeriodicThresholdFunc("storagedriver_"+app.Config.Storage.Type(), interval, app.Config.Health.StorageDriver.Threshold, storageDriverCheck)
   297  		} else {
   298  			healthRegistry.RegisterPeriodicFunc("storagedriver_"+app.Config.Storage.Type(), interval, storageDriverCheck)
   299  		}
   300  	}
   301  
   302  	for _, fileChecker := range app.Config.Health.FileCheckers {
   303  		interval := fileChecker.Interval
   304  		if interval == 0 {
   305  			interval = defaultCheckInterval
   306  		}
   307  		ctxu.GetLogger(app).Infof("configuring file health check path=%s, interval=%d", fileChecker.File, interval/time.Second)
   308  		healthRegistry.Register(fileChecker.File, health.PeriodicChecker(checks.FileChecker(fileChecker.File), interval))
   309  	}
   310  
   311  	for _, httpChecker := range app.Config.Health.HTTPCheckers {
   312  		interval := httpChecker.Interval
   313  		if interval == 0 {
   314  			interval = defaultCheckInterval
   315  		}
   316  
   317  		statusCode := httpChecker.StatusCode
   318  		if statusCode == 0 {
   319  			statusCode = 200
   320  		}
   321  
   322  		checker := checks.HTTPChecker(httpChecker.URI, statusCode, httpChecker.Timeout, httpChecker.Headers)
   323  
   324  		if httpChecker.Threshold != 0 {
   325  			ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d, threshold=%d", httpChecker.URI, interval/time.Second, httpChecker.Threshold)
   326  			healthRegistry.Register(httpChecker.URI, health.PeriodicThresholdChecker(checker, interval, httpChecker.Threshold))
   327  		} else {
   328  			ctxu.GetLogger(app).Infof("configuring HTTP health check uri=%s, interval=%d", httpChecker.URI, interval/time.Second)
   329  			healthRegistry.Register(httpChecker.URI, health.PeriodicChecker(checker, interval))
   330  		}
   331  	}
   332  
   333  	for _, tcpChecker := range app.Config.Health.TCPCheckers {
   334  		interval := tcpChecker.Interval
   335  		if interval == 0 {
   336  			interval = defaultCheckInterval
   337  		}
   338  
   339  		checker := checks.TCPChecker(tcpChecker.Addr, tcpChecker.Timeout)
   340  
   341  		if tcpChecker.Threshold != 0 {
   342  			ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d, threshold=%d", tcpChecker.Addr, interval/time.Second, tcpChecker.Threshold)
   343  			healthRegistry.Register(tcpChecker.Addr, health.PeriodicThresholdChecker(checker, interval, tcpChecker.Threshold))
   344  		} else {
   345  			ctxu.GetLogger(app).Infof("configuring TCP health check addr=%s, interval=%d", tcpChecker.Addr, interval/time.Second)
   346  			healthRegistry.Register(tcpChecker.Addr, health.PeriodicChecker(checker, interval))
   347  		}
   348  	}
   349  }
   350  
   351  // register a handler with the application, by route name. The handler will be
   352  // passed through the application filters and context will be constructed at
   353  // request time.
   354  func (app *App) register(routeName string, dispatch dispatchFunc) {
   355  
   356  	// TODO(stevvooe): This odd dispatcher/route registration is by-product of
   357  	// some limitations in the gorilla/mux router. We are using it to keep
   358  	// routing consistent between the client and server, but we may want to
   359  	// replace it with manual routing and structure-based dispatch for better
   360  	// control over the request execution.
   361  
   362  	app.router.GetRoute(routeName).Handler(app.dispatcher(dispatch))
   363  }
   364  
   365  // configureEvents prepares the event sink for action.
   366  func (app *App) configureEvents(configuration *configuration.Configuration) {
   367  	// Configure all of the endpoint sinks.
   368  	var sinks []notifications.Sink
   369  	for _, endpoint := range configuration.Notifications.Endpoints {
   370  		if endpoint.Disabled {
   371  			ctxu.GetLogger(app).Infof("endpoint %s disabled, skipping", endpoint.Name)
   372  			continue
   373  		}
   374  
   375  		ctxu.GetLogger(app).Infof("configuring endpoint %v (%v), timeout=%s, headers=%v", endpoint.Name, endpoint.URL, endpoint.Timeout, endpoint.Headers)
   376  		endpoint := notifications.NewEndpoint(endpoint.Name, endpoint.URL, notifications.EndpointConfig{
   377  			Timeout:   endpoint.Timeout,
   378  			Threshold: endpoint.Threshold,
   379  			Backoff:   endpoint.Backoff,
   380  			Headers:   endpoint.Headers,
   381  		})
   382  
   383  		sinks = append(sinks, endpoint)
   384  	}
   385  
   386  	// NOTE(stevvooe): Moving to a new queueing implementation is as easy as
   387  	// replacing broadcaster with a rabbitmq implementation. It's recommended
   388  	// that the registry instances also act as the workers to keep deployment
   389  	// simple.
   390  	app.events.sink = notifications.NewBroadcaster(sinks...)
   391  
   392  	// Populate registry event source
   393  	hostname, err := os.Hostname()
   394  	if err != nil {
   395  		hostname = configuration.HTTP.Addr
   396  	} else {
   397  		// try to pick the port off the config
   398  		_, port, err := net.SplitHostPort(configuration.HTTP.Addr)
   399  		if err == nil {
   400  			hostname = net.JoinHostPort(hostname, port)
   401  		}
   402  	}
   403  
   404  	app.events.source = notifications.SourceRecord{
   405  		Addr:       hostname,
   406  		InstanceID: ctxu.GetStringValue(app, "instance.id"),
   407  	}
   408  }
   409  
   410  func (app *App) configureRedis(configuration *configuration.Configuration) {
   411  	if configuration.Redis.Addr == "" {
   412  		ctxu.GetLogger(app).Infof("redis not configured")
   413  		return
   414  	}
   415  
   416  	pool := &redis.Pool{
   417  		Dial: func() (redis.Conn, error) {
   418  			// TODO(stevvooe): Yet another use case for contextual timing.
   419  			ctx := context.WithValue(app, "redis.connect.startedat", time.Now())
   420  
   421  			done := func(err error) {
   422  				logger := ctxu.GetLoggerWithField(ctx, "redis.connect.duration",
   423  					ctxu.Since(ctx, "redis.connect.startedat"))
   424  				if err != nil {
   425  					logger.Errorf("redis: error connecting: %v", err)
   426  				} else {
   427  					logger.Infof("redis: connect %v", configuration.Redis.Addr)
   428  				}
   429  			}
   430  
   431  			conn, err := redis.DialTimeout("tcp",
   432  				configuration.Redis.Addr,
   433  				configuration.Redis.DialTimeout,
   434  				configuration.Redis.ReadTimeout,
   435  				configuration.Redis.WriteTimeout)
   436  			if err != nil {
   437  				ctxu.GetLogger(app).Errorf("error connecting to redis instance %s: %v",
   438  					configuration.Redis.Addr, err)
   439  				done(err)
   440  				return nil, err
   441  			}
   442  
   443  			// authorize the connection
   444  			if configuration.Redis.Password != "" {
   445  				if _, err = conn.Do("AUTH", configuration.Redis.Password); err != nil {
   446  					defer conn.Close()
   447  					done(err)
   448  					return nil, err
   449  				}
   450  			}
   451  
   452  			// select the database to use
   453  			if configuration.Redis.DB != 0 {
   454  				if _, err = conn.Do("SELECT", configuration.Redis.DB); err != nil {
   455  					defer conn.Close()
   456  					done(err)
   457  					return nil, err
   458  				}
   459  			}
   460  
   461  			done(nil)
   462  			return conn, nil
   463  		},
   464  		MaxIdle:     configuration.Redis.Pool.MaxIdle,
   465  		MaxActive:   configuration.Redis.Pool.MaxActive,
   466  		IdleTimeout: configuration.Redis.Pool.IdleTimeout,
   467  		TestOnBorrow: func(c redis.Conn, t time.Time) error {
   468  			// TODO(stevvooe): We can probably do something more interesting
   469  			// here with the health package.
   470  			_, err := c.Do("PING")
   471  			return err
   472  		},
   473  		Wait: false, // if a connection is not avialable, proceed without cache.
   474  	}
   475  
   476  	app.redis = pool
   477  
   478  	// setup expvar
   479  	registry := expvar.Get("registry")
   480  	if registry == nil {
   481  		registry = expvar.NewMap("registry")
   482  	}
   483  
   484  	registry.(*expvar.Map).Set("redis", expvar.Func(func() interface{} {
   485  		return map[string]interface{}{
   486  			"Config": configuration.Redis,
   487  			"Active": app.redis.ActiveCount(),
   488  		}
   489  	}))
   490  }
   491  
   492  // configureLogHook prepares logging hook parameters.
   493  func (app *App) configureLogHook(configuration *configuration.Configuration) {
   494  	entry, ok := ctxu.GetLogger(app).(*log.Entry)
   495  	if !ok {
   496  		// somehow, we are not using logrus
   497  		return
   498  	}
   499  
   500  	logger := entry.Logger
   501  
   502  	for _, configHook := range configuration.Log.Hooks {
   503  		if !configHook.Disabled {
   504  			switch configHook.Type {
   505  			case "mail":
   506  				hook := &logHook{}
   507  				hook.LevelsParam = configHook.Levels
   508  				hook.Mail = &mailer{
   509  					Addr:     configHook.MailOptions.SMTP.Addr,
   510  					Username: configHook.MailOptions.SMTP.Username,
   511  					Password: configHook.MailOptions.SMTP.Password,
   512  					Insecure: configHook.MailOptions.SMTP.Insecure,
   513  					From:     configHook.MailOptions.From,
   514  					To:       configHook.MailOptions.To,
   515  				}
   516  				logger.Hooks.Add(hook)
   517  			default:
   518  			}
   519  		}
   520  	}
   521  }
   522  
   523  // configureSecret creates a random secret if a secret wasn't included in the
   524  // configuration.
   525  func (app *App) configureSecret(configuration *configuration.Configuration) {
   526  	if configuration.HTTP.Secret == "" {
   527  		var secretBytes [randomSecretSize]byte
   528  		if _, err := cryptorand.Read(secretBytes[:]); err != nil {
   529  			panic(fmt.Sprintf("could not generate random bytes for HTTP secret: %v", err))
   530  		}
   531  		configuration.HTTP.Secret = string(secretBytes[:])
   532  		ctxu.GetLogger(app).Warn("No HTTP secret provided - generated random secret. This may cause problems with uploads if multiple registries are behind a load-balancer. To provide a shared secret, fill in http.secret in the configuration file or set the REGISTRY_HTTP_SECRET environment variable.")
   533  	}
   534  }
   535  
   536  func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   537  	defer r.Body.Close() // ensure that request body is always closed.
   538  
   539  	// Instantiate an http context here so we can track the error codes
   540  	// returned by the request router.
   541  	ctx := defaultContextManager.context(app, w, r)
   542  
   543  	defer func() {
   544  		status, ok := ctx.Value("http.response.status").(int)
   545  		if ok && status >= 200 && status <= 399 {
   546  			ctxu.GetResponseLogger(ctx).Infof("response completed")
   547  		}
   548  	}()
   549  	defer defaultContextManager.release(ctx)
   550  
   551  	// NOTE(stevvooe): Total hack to get instrumented responsewriter from context.
   552  	var err error
   553  	w, err = ctxu.GetResponseWriter(ctx)
   554  	if err != nil {
   555  		ctxu.GetLogger(ctx).Warnf("response writer not found in context")
   556  	}
   557  
   558  	// Set a header with the Docker Distribution API Version for all responses.
   559  	w.Header().Add("Docker-Distribution-API-Version", "registry/2.0")
   560  	app.router.ServeHTTP(w, r)
   561  }
   562  
   563  // dispatchFunc takes a context and request and returns a constructed handler
   564  // for the route. The dispatcher will use this to dynamically create request
   565  // specific handlers for each endpoint without creating a new router for each
   566  // request.
   567  type dispatchFunc func(ctx *Context, r *http.Request) http.Handler
   568  
   569  // TODO(stevvooe): dispatchers should probably have some validation error
   570  // chain with proper error reporting.
   571  
   572  // dispatcher returns a handler that constructs a request specific context and
   573  // handler, using the dispatch factory function.
   574  func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
   575  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   576  		for headerName, headerValues := range app.Config.HTTP.Headers {
   577  			for _, value := range headerValues {
   578  				w.Header().Add(headerName, value)
   579  			}
   580  		}
   581  
   582  		context := app.context(w, r)
   583  
   584  		if err := app.authorized(w, r, context); err != nil {
   585  			ctxu.GetLogger(context).Warnf("error authorizing context: %v", err)
   586  			return
   587  		}
   588  
   589  		// Add username to request logging
   590  		context.Context = ctxu.WithLogger(context.Context, ctxu.GetLogger(context.Context, "auth.user.name"))
   591  
   592  		if app.nameRequired(r) {
   593  			repository, err := app.registry.Repository(context, getName(context))
   594  
   595  			if err != nil {
   596  				ctxu.GetLogger(context).Errorf("error resolving repository: %v", err)
   597  
   598  				switch err := err.(type) {
   599  				case distribution.ErrRepositoryUnknown:
   600  					context.Errors = append(context.Errors, v2.ErrorCodeNameUnknown.WithDetail(err))
   601  				case distribution.ErrRepositoryNameInvalid:
   602  					context.Errors = append(context.Errors, v2.ErrorCodeNameInvalid.WithDetail(err))
   603  				}
   604  
   605  				if err := errcode.ServeJSON(w, context.Errors); err != nil {
   606  					ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   607  				}
   608  				return
   609  			}
   610  
   611  			// assign and decorate the authorized repository with an event bridge.
   612  			context.Repository = notifications.Listen(
   613  				repository,
   614  				app.eventBridge(context, r))
   615  
   616  			context.Repository, err = applyRepoMiddleware(context.Context, context.Repository, app.Config.Middleware["repository"])
   617  			if err != nil {
   618  				ctxu.GetLogger(context).Errorf("error initializing repository middleware: %v", err)
   619  				context.Errors = append(context.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
   620  
   621  				if err := errcode.ServeJSON(w, context.Errors); err != nil {
   622  					ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   623  				}
   624  				return
   625  			}
   626  		}
   627  
   628  		dispatch(context, r).ServeHTTP(w, r)
   629  		// Automated error response handling here. Handlers may return their
   630  		// own errors if they need different behavior (such as range errors
   631  		// for layer upload).
   632  		if context.Errors.Len() > 0 {
   633  			if err := errcode.ServeJSON(w, context.Errors); err != nil {
   634  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   635  			}
   636  
   637  			app.logError(context, context.Errors)
   638  		}
   639  	})
   640  }
   641  
   642  func (app *App) logError(context context.Context, errors errcode.Errors) {
   643  	for _, e1 := range errors {
   644  		var c ctxu.Context
   645  
   646  		switch e1.(type) {
   647  		case errcode.Error:
   648  			e, _ := e1.(errcode.Error)
   649  			c = ctxu.WithValue(context, "err.code", e.Code)
   650  			c = ctxu.WithValue(c, "err.message", e.Code.Message())
   651  			c = ctxu.WithValue(c, "err.detail", e.Detail)
   652  		case errcode.ErrorCode:
   653  			e, _ := e1.(errcode.ErrorCode)
   654  			c = ctxu.WithValue(context, "err.code", e)
   655  			c = ctxu.WithValue(c, "err.message", e.Message())
   656  		default:
   657  			// just normal go 'error'
   658  			c = ctxu.WithValue(context, "err.code", errcode.ErrorCodeUnknown)
   659  			c = ctxu.WithValue(c, "err.message", e1.Error())
   660  		}
   661  
   662  		c = ctxu.WithLogger(c, ctxu.GetLogger(c,
   663  			"err.code",
   664  			"err.message",
   665  			"err.detail"))
   666  		ctxu.GetResponseLogger(c).Errorf("response completed with error")
   667  	}
   668  }
   669  
   670  // context constructs the context object for the application. This only be
   671  // called once per request.
   672  func (app *App) context(w http.ResponseWriter, r *http.Request) *Context {
   673  	ctx := defaultContextManager.context(app, w, r)
   674  	ctx = ctxu.WithVars(ctx, r)
   675  	ctx = ctxu.WithLogger(ctx, ctxu.GetLogger(ctx,
   676  		"vars.name",
   677  		"vars.reference",
   678  		"vars.digest",
   679  		"vars.uuid"))
   680  
   681  	context := &Context{
   682  		App:     app,
   683  		Context: ctx,
   684  	}
   685  
   686  	if app.httpHost.Scheme != "" && app.httpHost.Host != "" {
   687  		// A "host" item in the configuration takes precedence over
   688  		// X-Forwarded-Proto and X-Forwarded-Host headers, and the
   689  		// hostname in the request.
   690  		context.urlBuilder = v2.NewURLBuilder(&app.httpHost)
   691  	} else {
   692  		context.urlBuilder = v2.NewURLBuilderFromRequest(r)
   693  	}
   694  
   695  	return context
   696  }
   697  
   698  // authorized checks if the request can proceed with access to the requested
   699  // repository. If it succeeds, the context may access the requested
   700  // repository. An error will be returned if access is not available.
   701  func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Context) error {
   702  	ctxu.GetLogger(context).Debug("authorizing request")
   703  	repo := getName(context)
   704  
   705  	if app.accessController == nil {
   706  		return nil // access controller is not enabled.
   707  	}
   708  
   709  	var accessRecords []auth.Access
   710  
   711  	if repo != "" {
   712  		accessRecords = appendAccessRecords(accessRecords, r.Method, repo)
   713  	} else {
   714  		// Only allow the name not to be set on the base route.
   715  		if app.nameRequired(r) {
   716  			// For this to be properly secured, repo must always be set for a
   717  			// resource that may make a modification. The only condition under
   718  			// which name is not set and we still allow access is when the
   719  			// base route is accessed. This section prevents us from making
   720  			// that mistake elsewhere in the code, allowing any operation to
   721  			// proceed.
   722  			if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized); err != nil {
   723  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   724  			}
   725  			return fmt.Errorf("forbidden: no repository name")
   726  		}
   727  		accessRecords = appendCatalogAccessRecord(accessRecords, r)
   728  	}
   729  
   730  	ctx, err := app.accessController.Authorized(context.Context, accessRecords...)
   731  	if err != nil {
   732  		switch err := err.(type) {
   733  		case auth.Challenge:
   734  			// Add the appropriate WWW-Auth header
   735  			err.SetHeaders(w)
   736  
   737  			if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
   738  				ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
   739  			}
   740  		default:
   741  			// This condition is a potential security problem either in
   742  			// the configuration or whatever is backing the access
   743  			// controller. Just return a bad request with no information
   744  			// to avoid exposure. The request should not proceed.
   745  			ctxu.GetLogger(context).Errorf("error checking authorization: %v", err)
   746  			w.WriteHeader(http.StatusBadRequest)
   747  		}
   748  
   749  		return err
   750  	}
   751  
   752  	// TODO(stevvooe): This pattern needs to be cleaned up a bit. One context
   753  	// should be replaced by another, rather than replacing the context on a
   754  	// mutable object.
   755  	context.Context = ctx
   756  	return nil
   757  }
   758  
   759  // eventBridge returns a bridge for the current request, configured with the
   760  // correct actor and source.
   761  func (app *App) eventBridge(ctx *Context, r *http.Request) notifications.Listener {
   762  	actor := notifications.ActorRecord{
   763  		Name: getUserName(ctx, r),
   764  	}
   765  	request := notifications.NewRequestRecord(ctxu.GetRequestID(ctx), r)
   766  
   767  	return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
   768  }
   769  
   770  // nameRequired returns true if the route requires a name.
   771  func (app *App) nameRequired(r *http.Request) bool {
   772  	route := mux.CurrentRoute(r)
   773  	routeName := route.GetName()
   774  	return route == nil || (routeName != v2.RouteNameBase && routeName != v2.RouteNameCatalog)
   775  }
   776  
   777  // apiBase implements a simple yes-man for doing overall checks against the
   778  // api. This can support auth roundtrips to support docker login.
   779  func apiBase(w http.ResponseWriter, r *http.Request) {
   780  	const emptyJSON = "{}"
   781  	// Provide a simple /v2/ 200 OK response with empty json response.
   782  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   783  	w.Header().Set("Content-Length", fmt.Sprint(len(emptyJSON)))
   784  
   785  	fmt.Fprint(w, emptyJSON)
   786  }
   787  
   788  // appendAccessRecords checks the method and adds the appropriate Access records to the records list.
   789  func appendAccessRecords(records []auth.Access, method string, repo string) []auth.Access {
   790  	resource := auth.Resource{
   791  		Type: "repository",
   792  		Name: repo,
   793  	}
   794  
   795  	switch method {
   796  	case "GET", "HEAD":
   797  		records = append(records,
   798  			auth.Access{
   799  				Resource: resource,
   800  				Action:   "pull",
   801  			})
   802  	case "POST", "PUT", "PATCH":
   803  		records = append(records,
   804  			auth.Access{
   805  				Resource: resource,
   806  				Action:   "pull",
   807  			},
   808  			auth.Access{
   809  				Resource: resource,
   810  				Action:   "push",
   811  			})
   812  	case "DELETE":
   813  		// DELETE access requires full admin rights, which is represented
   814  		// as "*". This may not be ideal.
   815  		records = append(records,
   816  			auth.Access{
   817  				Resource: resource,
   818  				Action:   "*",
   819  			})
   820  	}
   821  	return records
   822  }
   823  
   824  // Add the access record for the catalog if it's our current route
   825  func appendCatalogAccessRecord(accessRecords []auth.Access, r *http.Request) []auth.Access {
   826  	route := mux.CurrentRoute(r)
   827  	routeName := route.GetName()
   828  
   829  	if routeName == v2.RouteNameCatalog {
   830  		resource := auth.Resource{
   831  			Type: "registry",
   832  			Name: "catalog",
   833  		}
   834  
   835  		accessRecords = append(accessRecords,
   836  			auth.Access{
   837  				Resource: resource,
   838  				Action:   "*",
   839  			})
   840  	}
   841  	return accessRecords
   842  }
   843  
   844  // applyRegistryMiddleware wraps a registry instance with the configured middlewares
   845  func applyRegistryMiddleware(ctx context.Context, registry distribution.Namespace, middlewares []configuration.Middleware) (distribution.Namespace, error) {
   846  	for _, mw := range middlewares {
   847  		rmw, err := registrymiddleware.Get(ctx, mw.Name, mw.Options, registry)
   848  		if err != nil {
   849  			return nil, fmt.Errorf("unable to configure registry middleware (%s): %s", mw.Name, err)
   850  		}
   851  		registry = rmw
   852  	}
   853  	return registry, nil
   854  
   855  }
   856  
   857  // applyRepoMiddleware wraps a repository with the configured middlewares
   858  func applyRepoMiddleware(ctx context.Context, repository distribution.Repository, middlewares []configuration.Middleware) (distribution.Repository, error) {
   859  	for _, mw := range middlewares {
   860  		rmw, err := repositorymiddleware.Get(ctx, mw.Name, mw.Options, repository)
   861  		if err != nil {
   862  			return nil, err
   863  		}
   864  		repository = rmw
   865  	}
   866  	return repository, nil
   867  }
   868  
   869  // applyStorageMiddleware wraps a storage driver with the configured middlewares
   870  func applyStorageMiddleware(driver storagedriver.StorageDriver, middlewares []configuration.Middleware) (storagedriver.StorageDriver, error) {
   871  	for _, mw := range middlewares {
   872  		smw, err := storagemiddleware.Get(mw.Name, mw.Options, driver)
   873  		if err != nil {
   874  			return nil, fmt.Errorf("unable to configure storage middleware (%s): %v", mw.Name, err)
   875  		}
   876  		driver = smw
   877  	}
   878  	return driver, nil
   879  }
   880  
   881  // uploadPurgeDefaultConfig provides a default configuration for upload
   882  // purging to be used in the absence of configuration in the
   883  // confifuration file
   884  func uploadPurgeDefaultConfig() map[interface{}]interface{} {
   885  	config := map[interface{}]interface{}{}
   886  	config["enabled"] = true
   887  	config["age"] = "168h"
   888  	config["interval"] = "24h"
   889  	config["dryrun"] = false
   890  	return config
   891  }
   892  
   893  func badPurgeUploadConfig(reason string) {
   894  	panic(fmt.Sprintf("Unable to parse upload purge configuration: %s", reason))
   895  }
   896  
   897  // startUploadPurger schedules a goroutine which will periodically
   898  // check upload directories for old files and delete them
   899  func startUploadPurger(ctx context.Context, storageDriver storagedriver.StorageDriver, log ctxu.Logger, config map[interface{}]interface{}) {
   900  	if config["enabled"] == false {
   901  		return
   902  	}
   903  
   904  	var purgeAgeDuration time.Duration
   905  	var err error
   906  	purgeAge, ok := config["age"]
   907  	if ok {
   908  		ageStr, ok := purgeAge.(string)
   909  		if !ok {
   910  			badPurgeUploadConfig("age is not a string")
   911  		}
   912  		purgeAgeDuration, err = time.ParseDuration(ageStr)
   913  		if err != nil {
   914  			badPurgeUploadConfig(fmt.Sprintf("Cannot parse duration: %s", err.Error()))
   915  		}
   916  	} else {
   917  		badPurgeUploadConfig("age missing")
   918  	}
   919  
   920  	var intervalDuration time.Duration
   921  	interval, ok := config["interval"]
   922  	if ok {
   923  		intervalStr, ok := interval.(string)
   924  		if !ok {
   925  			badPurgeUploadConfig("interval is not a string")
   926  		}
   927  
   928  		intervalDuration, err = time.ParseDuration(intervalStr)
   929  		if err != nil {
   930  			badPurgeUploadConfig(fmt.Sprintf("Cannot parse interval: %s", err.Error()))
   931  		}
   932  	} else {
   933  		badPurgeUploadConfig("interval missing")
   934  	}
   935  
   936  	var dryRunBool bool
   937  	dryRun, ok := config["dryrun"]
   938  	if ok {
   939  		dryRunBool, ok = dryRun.(bool)
   940  		if !ok {
   941  			badPurgeUploadConfig("cannot parse dryrun")
   942  		}
   943  	} else {
   944  		badPurgeUploadConfig("dryrun missing")
   945  	}
   946  
   947  	go func() {
   948  		rand.Seed(time.Now().Unix())
   949  		jitter := time.Duration(rand.Int()%60) * time.Minute
   950  		log.Infof("Starting upload purge in %s", jitter)
   951  		time.Sleep(jitter)
   952  
   953  		for {
   954  			storage.PurgeUploads(ctx, storageDriver, time.Now().Add(-purgeAgeDuration), !dryRunBool)
   955  			log.Infof("Starting upload purge in %s", intervalDuration)
   956  			time.Sleep(intervalDuration)
   957  		}
   958  	}()
   959  }