@@ -19,39 +19,99 @@ package main
1919import (
2020 "fmt"
2121 "log"
22+ "log/slog"
2223 "net/http"
24+ "os"
2325 "strings"
2426
2527 "aidanwoods.dev/go-paseto"
28+ "github.com/urfave/cli/v2"
2629)
2730
28- type originalHostContextKey string
29-
3031const (
31- ctxKeyOriginalHost = originalHostContextKey ("original-host" )
3232 proxyConfigHeader string = "X-Proxy-Config"
3333 tokenKeyUpstreamToken string = "upstream-token"
3434)
3535
3636var (
37+ // version, commit, date, builtBy are provided by goreleaser during build
38+ version = "dev"
39+ commit = "dev"
40+ date = "unknown"
41+ builtBy = "unknown"
42+
3743 tokenEndpoints = map [string ]* WWWAuthenticateData {} // mapping of registryHosts to token endpoint URLs
44+ logger * slog.Logger
45+ logLevel * slog.LevelVar
3846)
3947
48+ func init () {
49+ logLevel = new (slog.LevelVar )
50+
51+ cli .VersionPrinter = func (c * cli.Context ) {
52+ fmt .Printf ("registryproxy version %s; commit %s; built on %s; by %s\n " , version , commit , date , builtBy )
53+ }
54+ }
55+
4056func main () {
57+ app := & cli.App {
58+ Name : "registryproxy" ,
59+ Version : version ,
60+ Usage : "reverse proxy for container image registries (like Docker Hub)" ,
61+ Flags : []cli.Flag {
62+ & cli.StringFlag {
63+ Name : "config" ,
64+ Value : "" ,
65+ Usage : "path to the config file" ,
66+ },
67+ & cli.StringFlag {
68+ Name : "loglevel" ,
69+ Value : "INFO" ,
70+ Usage : "how verbosely to log, one of: DEBUG, INFO, WARN, ERROR" ,
71+ },
72+ },
73+ Action : func (ctx * cli.Context ) error {
74+ setLogLevel (ctx .String ("loglevel" ))
75+ logger = slog .New (slog .NewTextHandler (
76+ os .Stderr ,
77+ & slog.HandlerOptions {
78+ Level : logLevel ,
79+ }),
80+ )
81+ logger .Info ("registryproxy starting up" ,
82+ "version" , version ,
83+ "commit" , commit ,
84+ "date" , date ,
85+ "builder" , builtBy ,
86+ )
87+ Serve (ctx .String ("config" ))
88+ return nil
89+ },
90+ }
91+
92+ if err := app .Run (os .Args ); err != nil {
93+ log .Fatal (err )
94+ }
95+ }
96+
97+ func Serve (configPath string ) {
4198 // load the yaml config file
42- config , err := LoadConfig ()
99+ config , err := LoadConfig (configPath )
43100 if err != nil {
44- log .Fatalf ("config loading error: %s" , err )
101+ logger .Error ("config loading error" , "error" , err )
102+ os .Exit (1 )
45103 }
46104 config .Log ()
47105
48106 // the secret key is used to process the PASETO tokens we issue to clients
49107 if config .SecretKey == "" {
50- log .Fatal ("SecretKey not found in config" )
108+ logger .Error ("SecretKey not found in config" )
109+ os .Exit (1 )
51110 }
52111 pasetoSecretKey , err := paseto .V4SymmetricKeyFromHex (config .SecretKey )
53112 if err != nil {
54- log .Fatalf ("Failed to parse PASETO symmetric key; err: %s" , err )
113+ logger .Error ("Failed to parse PASETO symmetric key" , "err" , err )
114+ os .Exit (1 )
55115 }
56116
57117 // set up http handlers for each proxy
@@ -65,23 +125,42 @@ func main() {
65125 // tokenEndpoints we look it up, then add it
66126 endpoint , err := DiscoverTokenEndpoint (proxy .RegistryHost )
67127 if err != nil {
68- log .Fatalf ("unable to discover token endpoint corresponding to the registry:%s; err:%s" , proxy .RegistryHost , err )
128+ logger .Error ("unable to discover token endpoint" , "registry" , proxy .RegistryHost , "error" , err )
129+ os .Exit (1 )
130+
69131 }
70132 tokenEndpoints [proxy .RegistryHost ] = endpoint
71133 }
72134
73135 proxyPath := fmt .Sprintf ("/v2/%s/" , strings .Trim (proxy .LocalPrefix , "/" ))
74- log . Printf ("setup handler for path:%s pointing to proxy: %s " , proxyPath , proxy .LocalPrefix )
75- mux .Handle (proxyPath , NewRegistryProxy (proxy , pasetoSecretKey ))
136+ logger . Info ("setup handler" , " path" , proxyPath , "proxy" , proxy .LocalPrefix )
137+ mux .Handle (proxyPath , NewRegistryProxy (proxy , pasetoSecretKey , config . ProxyFQDN ))
76138 }
77139
78140 // serve
79141 hostport := fmt .Sprintf ("%s:%s" , config .ListenAddr , config .ListenPort )
80- log .Printf ("starting to listen on %s" , hostport )
142+ logger .Info ("listening for network connections" , "addr" , hostport )
143+
144+ if err := http .ListenAndServe (hostport , PanicLogger (mux )); err != http .ErrServerClosed {
145+ logger .Error ("unable to start network listener" , "error" , err )
146+ os .Exit (1 )
81147
82- if err := http .ListenAndServe (hostport , PanicLogger (CaptureHostHeader (mux ))); err != http .ErrServerClosed {
83- log .Fatalf ("listen error: %+v" , err )
84148 }
85149
86- log .Printf ("server shutdown successfully" )
150+ logger .Info ("server shutdown successfully" )
151+ }
152+
153+ // PanicLogger intends to log something when an http handler panics
154+ func PanicLogger (next http.Handler ) http.Handler {
155+ // Note: this needs testing/validation, the entire concept of this middleware
156+ // may be the result of several wrong assumptions
157+ return http .HandlerFunc (func (rw http.ResponseWriter , req * http.Request ) {
158+ defer func () {
159+ if err := recover (); err != nil {
160+ logger .Error ("PanicLogger recovered in HTTP handler" , "handler" , err )
161+ http .Error (rw , "Internal Server Error" , http .StatusInternalServerError )
162+ }
163+ }()
164+ next .ServeHTTP (rw , req )
165+ })
87166}
0 commit comments