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 }