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

     1  package registry
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"time"
    11  
    12  	log "github.com/Sirupsen/logrus"
    13  	"github.com/Sirupsen/logrus/formatters/logstash"
    14  	"github.com/bugsnag/bugsnag-go"
    15  	"github.com/docker/distribution/configuration"
    16  	"github.com/docker/distribution/context"
    17  	"github.com/docker/distribution/health"
    18  	"github.com/docker/distribution/registry/handlers"
    19  	"github.com/docker/distribution/registry/listener"
    20  	"github.com/docker/distribution/uuid"
    21  	"github.com/docker/distribution/version"
    22  	gorhandlers "github.com/gorilla/handlers"
    23  	"github.com/spf13/cobra"
    24  	"github.com/yvasiyarov/gorelic"
    25  )
    26  
    27  // Cmd is a cobra command for running the registry.
    28  var Cmd = &cobra.Command{
    29  	Use:   "registry <config>",
    30  	Short: "registry stores and distributes Docker images",
    31  	Long:  "registry stores and distributes Docker images.",
    32  	Run: func(cmd *cobra.Command, args []string) {
    33  		if showVersion {
    34  			version.PrintVersion()
    35  			return
    36  		}
    37  
    38  		// setup context
    39  		ctx := context.WithVersion(context.Background(), version.Version)
    40  
    41  		config, err := resolveConfiguration(args)
    42  		if err != nil {
    43  			fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)
    44  			cmd.Usage()
    45  			os.Exit(1)
    46  		}
    47  
    48  		if config.HTTP.Debug.Addr != "" {
    49  			go func(addr string) {
    50  				log.Infof("debug server listening %v", addr)
    51  				if err := http.ListenAndServe(addr, nil); err != nil {
    52  					log.Fatalf("error listening on debug interface: %v", err)
    53  				}
    54  			}(config.HTTP.Debug.Addr)
    55  		}
    56  
    57  		registry, err := NewRegistry(ctx, config)
    58  		if err != nil {
    59  			log.Fatalln(err)
    60  		}
    61  
    62  		if err = registry.ListenAndServe(); err != nil {
    63  			log.Fatalln(err)
    64  		}
    65  	},
    66  }
    67  
    68  var showVersion bool
    69  
    70  func init() {
    71  	Cmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
    72  }
    73  
    74  // A Registry represents a complete instance of the registry.
    75  // TODO(aaronl): It might make sense for Registry to become an interface.
    76  type Registry struct {
    77  	config *configuration.Configuration
    78  	app    *handlers.App
    79  	server *http.Server
    80  }
    81  
    82  // NewRegistry creates a new registry from a context and configuration struct.
    83  func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Registry, error) {
    84  	var err error
    85  	ctx, err = configureLogging(ctx, config)
    86  	if err != nil {
    87  		return nil, fmt.Errorf("error configuring logger: %v", err)
    88  	}
    89  
    90  	// inject a logger into the uuid library. warns us if there is a problem
    91  	// with uuid generation under low entropy.
    92  	uuid.Loggerf = context.GetLogger(ctx).Warnf
    93  
    94  	app := handlers.NewApp(ctx, config)
    95  	// TODO(aaronl): The global scope of the health checks means NewRegistry
    96  	// can only be called once per process.
    97  	app.RegisterHealthChecks()
    98  	handler := configureReporting(app)
    99  	handler = alive("/", handler)
   100  	handler = health.Handler(handler)
   101  	handler = panicHandler(handler)
   102  	handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
   103  
   104  	server := &http.Server{
   105  		Handler: handler,
   106  	}
   107  
   108  	return &Registry{
   109  		app:    app,
   110  		config: config,
   111  		server: server,
   112  	}, nil
   113  }
   114  
   115  // ListenAndServe runs the registry's HTTP server.
   116  func (registry *Registry) ListenAndServe() error {
   117  	config := registry.config
   118  
   119  	ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	if config.HTTP.TLS.Certificate != "" {
   125  		tlsConf := &tls.Config{
   126  			ClientAuth:               tls.NoClientCert,
   127  			NextProtos:               []string{"http/1.1"},
   128  			Certificates:             make([]tls.Certificate, 1),
   129  			MinVersion:               tls.VersionTLS10,
   130  			PreferServerCipherSuites: true,
   131  			CipherSuites: []uint16{
   132  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
   133  				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   134  				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
   135  				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
   136  				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
   137  				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
   138  				tls.TLS_RSA_WITH_AES_128_CBC_SHA,
   139  				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
   140  			},
   141  		}
   142  
   143  		tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key)
   144  		if err != nil {
   145  			return err
   146  		}
   147  
   148  		if len(config.HTTP.TLS.ClientCAs) != 0 {
   149  			pool := x509.NewCertPool()
   150  
   151  			for _, ca := range config.HTTP.TLS.ClientCAs {
   152  				caPem, err := ioutil.ReadFile(ca)
   153  				if err != nil {
   154  					return err
   155  				}
   156  
   157  				if ok := pool.AppendCertsFromPEM(caPem); !ok {
   158  					return fmt.Errorf("Could not add CA to pool")
   159  				}
   160  			}
   161  
   162  			for _, subj := range pool.Subjects() {
   163  				context.GetLogger(registry.app).Debugf("CA Subject: %s", string(subj))
   164  			}
   165  
   166  			tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
   167  			tlsConf.ClientCAs = pool
   168  		}
   169  
   170  		ln = tls.NewListener(ln, tlsConf)
   171  		context.GetLogger(registry.app).Infof("listening on %v, tls", ln.Addr())
   172  	} else {
   173  		context.GetLogger(registry.app).Infof("listening on %v", ln.Addr())
   174  	}
   175  
   176  	return registry.server.Serve(ln)
   177  }
   178  
   179  func configureReporting(app *handlers.App) http.Handler {
   180  	var handler http.Handler = app
   181  
   182  	if app.Config.Reporting.Bugsnag.APIKey != "" {
   183  		bugsnagConfig := bugsnag.Configuration{
   184  			APIKey: app.Config.Reporting.Bugsnag.APIKey,
   185  			// TODO(brianbland): provide the registry version here
   186  			// AppVersion: "2.0",
   187  		}
   188  		if app.Config.Reporting.Bugsnag.ReleaseStage != "" {
   189  			bugsnagConfig.ReleaseStage = app.Config.Reporting.Bugsnag.ReleaseStage
   190  		}
   191  		if app.Config.Reporting.Bugsnag.Endpoint != "" {
   192  			bugsnagConfig.Endpoint = app.Config.Reporting.Bugsnag.Endpoint
   193  		}
   194  		bugsnag.Configure(bugsnagConfig)
   195  
   196  		handler = bugsnag.Handler(handler)
   197  	}
   198  
   199  	if app.Config.Reporting.NewRelic.LicenseKey != "" {
   200  		agent := gorelic.NewAgent()
   201  		agent.NewrelicLicense = app.Config.Reporting.NewRelic.LicenseKey
   202  		if app.Config.Reporting.NewRelic.Name != "" {
   203  			agent.NewrelicName = app.Config.Reporting.NewRelic.Name
   204  		}
   205  		agent.CollectHTTPStat = true
   206  		agent.Verbose = app.Config.Reporting.NewRelic.Verbose
   207  		agent.Run()
   208  
   209  		handler = agent.WrapHTTPHandler(handler)
   210  	}
   211  
   212  	return handler
   213  }
   214  
   215  // configureLogging prepares the context with a logger using the
   216  // configuration.
   217  func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) {
   218  	if config.Log.Level == "" && config.Log.Formatter == "" {
   219  		// If no config for logging is set, fallback to deprecated "Loglevel".
   220  		log.SetLevel(logLevel(config.Loglevel))
   221  		ctx = context.WithLogger(ctx, context.GetLogger(ctx))
   222  		return ctx, nil
   223  	}
   224  
   225  	log.SetLevel(logLevel(config.Log.Level))
   226  
   227  	formatter := config.Log.Formatter
   228  	if formatter == "" {
   229  		formatter = "text" // default formatter
   230  	}
   231  
   232  	switch formatter {
   233  	case "json":
   234  		log.SetFormatter(&log.JSONFormatter{
   235  			TimestampFormat: time.RFC3339Nano,
   236  		})
   237  	case "text":
   238  		log.SetFormatter(&log.TextFormatter{
   239  			TimestampFormat: time.RFC3339Nano,
   240  		})
   241  	case "logstash":
   242  		log.SetFormatter(&logstash.LogstashFormatter{
   243  			TimestampFormat: time.RFC3339Nano,
   244  		})
   245  	default:
   246  		// just let the library use default on empty string.
   247  		if config.Log.Formatter != "" {
   248  			return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter)
   249  		}
   250  	}
   251  
   252  	if config.Log.Formatter != "" {
   253  		log.Debugf("using %q logging formatter", config.Log.Formatter)
   254  	}
   255  
   256  	if len(config.Log.Fields) > 0 {
   257  		// build up the static fields, if present.
   258  		var fields []interface{}
   259  		for k := range config.Log.Fields {
   260  			fields = append(fields, k)
   261  		}
   262  
   263  		ctx = context.WithValues(ctx, config.Log.Fields)
   264  		ctx = context.WithLogger(ctx, context.GetLogger(ctx, fields...))
   265  	}
   266  
   267  	return ctx, nil
   268  }
   269  
   270  func logLevel(level configuration.Loglevel) log.Level {
   271  	l, err := log.ParseLevel(string(level))
   272  	if err != nil {
   273  		l = log.InfoLevel
   274  		log.Warnf("error parsing level %q: %v, using %q	", level, err, l)
   275  	}
   276  
   277  	return l
   278  }
   279  
   280  // panicHandler add a HTTP handler to web app. The handler recover the happening
   281  // panic. logrus.Panic transmits panic message to pre-config log hooks, which is
   282  // defined in config.yml.
   283  func panicHandler(handler http.Handler) http.Handler {
   284  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   285  		defer func() {
   286  			if err := recover(); err != nil {
   287  				log.Panic(fmt.Sprintf("%v", err))
   288  			}
   289  		}()
   290  		handler.ServeHTTP(w, r)
   291  	})
   292  }
   293  
   294  // alive simply wraps the handler with a route that always returns an http 200
   295  // response when the path is matched. If the path is not matched, the request
   296  // is passed to the provided handler. There is no guarantee of anything but
   297  // that the server is up. Wrap with other handlers (such as health.Handler)
   298  // for greater affect.
   299  func alive(path string, handler http.Handler) http.Handler {
   300  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   301  		if r.URL.Path == path {
   302  			w.Header().Set("Cache-Control", "no-cache")
   303  			w.WriteHeader(http.StatusOK)
   304  			return
   305  		}
   306  
   307  		handler.ServeHTTP(w, r)
   308  	})
   309  }
   310  
   311  func resolveConfiguration(args []string) (*configuration.Configuration, error) {
   312  	var configurationPath string
   313  
   314  	if len(args) > 0 {
   315  		configurationPath = args[0]
   316  	} else if os.Getenv("REGISTRY_CONFIGURATION_PATH") != "" {
   317  		configurationPath = os.Getenv("REGISTRY_CONFIGURATION_PATH")
   318  	}
   319  
   320  	if configurationPath == "" {
   321  		return nil, fmt.Errorf("configuration path unspecified")
   322  	}
   323  
   324  	fp, err := os.Open(configurationPath)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	defer fp.Close()
   330  
   331  	config, err := configuration.Parse(fp)
   332  	if err != nil {
   333  		return nil, fmt.Errorf("error parsing %s: %v", configurationPath, err)
   334  	}
   335  
   336  	return config, nil
   337  }