github.com/lusis/distribution@v2.0.1+incompatible/cmd/registry/main.go (about) 1 package main 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 _ "expvar" 7 "flag" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 _ "net/http/pprof" 12 "os" 13 "time" 14 15 log "github.com/Sirupsen/logrus" 16 "github.com/Sirupsen/logrus/formatters/logstash" 17 "github.com/bugsnag/bugsnag-go" 18 "github.com/docker/distribution/configuration" 19 "github.com/docker/distribution/context" 20 _ "github.com/docker/distribution/health" 21 _ "github.com/docker/distribution/registry/auth/silly" 22 _ "github.com/docker/distribution/registry/auth/token" 23 "github.com/docker/distribution/registry/handlers" 24 _ "github.com/docker/distribution/registry/storage/driver/azure" 25 _ "github.com/docker/distribution/registry/storage/driver/filesystem" 26 _ "github.com/docker/distribution/registry/storage/driver/inmemory" 27 _ "github.com/docker/distribution/registry/storage/driver/middleware/cloudfront" 28 _ "github.com/docker/distribution/registry/storage/driver/s3" 29 "github.com/docker/distribution/version" 30 gorhandlers "github.com/gorilla/handlers" 31 "github.com/yvasiyarov/gorelic" 32 ) 33 34 var showVersion bool 35 36 func init() { 37 flag.BoolVar(&showVersion, "version", false, "show the version and exit") 38 } 39 40 func main() { 41 flag.Usage = usage 42 flag.Parse() 43 44 if showVersion { 45 version.PrintVersion() 46 return 47 } 48 49 ctx := context.Background() 50 ctx = context.WithValue(ctx, "version", version.Version) 51 52 config, err := resolveConfiguration() 53 if err != nil { 54 fatalf("configuration error: %v", err) 55 } 56 57 ctx, err = configureLogging(ctx, config) 58 if err != nil { 59 fatalf("error configuring logger: %v", err) 60 } 61 62 app := handlers.NewApp(ctx, *config) 63 handler := configureReporting(app) 64 handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler) 65 66 if config.HTTP.Debug.Addr != "" { 67 go debugServer(config.HTTP.Debug.Addr) 68 } 69 70 if config.HTTP.TLS.Certificate == "" { 71 context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr) 72 if err := http.ListenAndServe(config.HTTP.Addr, handler); err != nil { 73 context.GetLogger(app).Fatalln(err) 74 } 75 } else { 76 tlsConf := &tls.Config{ 77 ClientAuth: tls.NoClientCert, 78 } 79 80 if len(config.HTTP.TLS.ClientCAs) != 0 { 81 pool := x509.NewCertPool() 82 83 for _, ca := range config.HTTP.TLS.ClientCAs { 84 caPem, err := ioutil.ReadFile(ca) 85 if err != nil { 86 context.GetLogger(app).Fatalln(err) 87 } 88 89 if ok := pool.AppendCertsFromPEM(caPem); !ok { 90 context.GetLogger(app).Fatalln(fmt.Errorf("Could not add CA to pool")) 91 } 92 } 93 94 for _, subj := range pool.Subjects() { 95 context.GetLogger(app).Debugf("CA Subject: %s", string(subj)) 96 } 97 98 tlsConf.ClientAuth = tls.RequireAndVerifyClientCert 99 tlsConf.ClientCAs = pool 100 } 101 102 context.GetLogger(app).Infof("listening on %v, tls", config.HTTP.Addr) 103 server := &http.Server{ 104 Addr: config.HTTP.Addr, 105 Handler: handler, 106 TLSConfig: tlsConf, 107 } 108 109 if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { 110 context.GetLogger(app).Fatalln(err) 111 } 112 } 113 } 114 115 func usage() { 116 fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>") 117 flag.PrintDefaults() 118 } 119 120 func fatalf(format string, args ...interface{}) { 121 fmt.Fprintf(os.Stderr, format+"\n", args...) 122 usage() 123 os.Exit(1) 124 } 125 126 func resolveConfiguration() (*configuration.Configuration, error) { 127 var configurationPath string 128 129 if flag.NArg() > 0 { 130 configurationPath = flag.Arg(0) 131 } else if os.Getenv("REGISTRY_CONFIGURATION_PATH") != "" { 132 configurationPath = os.Getenv("REGISTRY_CONFIGURATION_PATH") 133 } 134 135 if configurationPath == "" { 136 return nil, fmt.Errorf("configuration path unspecified") 137 } 138 139 fp, err := os.Open(configurationPath) 140 if err != nil { 141 return nil, err 142 } 143 144 config, err := configuration.Parse(fp) 145 if err != nil { 146 return nil, fmt.Errorf("error parsing %s: %v", configurationPath, err) 147 } 148 149 return config, nil 150 } 151 152 func configureReporting(app *handlers.App) http.Handler { 153 var handler http.Handler = app 154 155 if app.Config.Reporting.Bugsnag.APIKey != "" { 156 bugsnagConfig := bugsnag.Configuration{ 157 APIKey: app.Config.Reporting.Bugsnag.APIKey, 158 // TODO(brianbland): provide the registry version here 159 // AppVersion: "2.0", 160 } 161 if app.Config.Reporting.Bugsnag.ReleaseStage != "" { 162 bugsnagConfig.ReleaseStage = app.Config.Reporting.Bugsnag.ReleaseStage 163 } 164 if app.Config.Reporting.Bugsnag.Endpoint != "" { 165 bugsnagConfig.Endpoint = app.Config.Reporting.Bugsnag.Endpoint 166 } 167 bugsnag.Configure(bugsnagConfig) 168 169 handler = bugsnag.Handler(handler) 170 } 171 172 if app.Config.Reporting.NewRelic.LicenseKey != "" { 173 agent := gorelic.NewAgent() 174 agent.NewrelicLicense = app.Config.Reporting.NewRelic.LicenseKey 175 if app.Config.Reporting.NewRelic.Name != "" { 176 agent.NewrelicName = app.Config.Reporting.NewRelic.Name 177 } 178 agent.CollectHTTPStat = true 179 agent.Verbose = app.Config.Reporting.NewRelic.Verbose 180 agent.Run() 181 182 handler = agent.WrapHTTPHandler(handler) 183 } 184 185 return handler 186 } 187 188 // configureLogging prepares the context with a logger using the 189 // configuration. 190 func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) { 191 if config.Log.Level == "" && config.Log.Formatter == "" { 192 // If no config for logging is set, fallback to deprecated "Loglevel". 193 log.SetLevel(logLevel(config.Loglevel)) 194 ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version")) 195 return ctx, nil 196 } 197 198 log.SetLevel(logLevel(config.Log.Level)) 199 200 formatter := config.Log.Formatter 201 if formatter == "" { 202 formatter = "text" // default formatter 203 } 204 205 switch formatter { 206 case "json": 207 log.SetFormatter(&log.JSONFormatter{ 208 TimestampFormat: time.RFC3339Nano, 209 }) 210 case "text": 211 log.SetFormatter(&log.TextFormatter{ 212 TimestampFormat: time.RFC3339Nano, 213 }) 214 case "logstash": 215 log.SetFormatter(&logstash.LogstashFormatter{ 216 TimestampFormat: time.RFC3339Nano, 217 }) 218 default: 219 // just let the library use default on empty string. 220 if config.Log.Formatter != "" { 221 return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter) 222 } 223 } 224 225 if config.Log.Formatter != "" { 226 log.Debugf("using %q logging formatter", config.Log.Formatter) 227 } 228 229 // log the application version with messages 230 ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version")) 231 232 if len(config.Log.Fields) > 0 { 233 // build up the static fields, if present. 234 var fields []interface{} 235 for k := range config.Log.Fields { 236 fields = append(fields, k) 237 } 238 239 ctx = context.WithValues(ctx, config.Log.Fields) 240 ctx = context.WithLogger(ctx, context.GetLogger(ctx, fields...)) 241 } 242 243 return ctx, nil 244 } 245 246 func logLevel(level configuration.Loglevel) log.Level { 247 l, err := log.ParseLevel(string(level)) 248 if err != nil { 249 l = log.InfoLevel 250 log.Warnf("error parsing level %q: %v, using %q ", level, err, l) 251 } 252 253 return l 254 } 255 256 // debugServer starts the debug server with pprof, expvar among other 257 // endpoints. The addr should not be exposed externally. For most of these to 258 // work, tls cannot be enabled on the endpoint, so it is generally separate. 259 func debugServer(addr string) { 260 log.Infof("debug server listening %v", addr) 261 if err := http.ListenAndServe(addr, nil); err != nil { 262 log.Fatalf("error listening on debug interface: %v", err) 263 } 264 }