Skip to content

Commit 939cfec

Browse files
authored
Merge pull request #14 from backplane/slog
* adopts slog from stlib, therefore closes #7 * adopts urfave/cli/v2, therefore closes #5 * instead of using the host header, we specify the fqdn we intend to serve in the config file
2 parents 42b1e7e + ae7efe2 commit 939cfec

9 files changed

Lines changed: 200 additions & 113 deletions

File tree

config.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,36 @@ package main
33
import (
44
"encoding/json"
55
"fmt"
6-
"log"
76
"os"
87
"strings"
98

109
"gopkg.in/yaml.v2"
1110
)
1211

1312
type ProxyItem struct {
14-
RegistryHost string `yaml:"registry"`
15-
RemotePrefix string `yaml:"remote"`
16-
LocalPrefix string `yaml:"-"` // this is set from the item name
17-
AuthHeader string `yaml:"auth"`
13+
RegistryHost string `yaml:"registry" json:"registry"`
14+
RemotePrefix string `yaml:"remote" json:"remote"`
15+
LocalPrefix string `yaml:"-" json:"-"` // this is set from the item name
16+
AuthHeader string `yaml:"auth" json:"auth"`
1817
}
1918

2019
type Config struct {
21-
ListenAddr string `yaml:"listen_addr"`
22-
ListenPort string `yaml:"listen_port"`
23-
Proxies map[string]ProxyItem `yaml:"proxies"`
24-
SecretKey string `yaml:"secretkey"`
20+
ListenAddr string `yaml:"listen_addr" json:"listen_addr"`
21+
ListenPort string `yaml:"listen_port" json:"listen_port"`
22+
ProxyFQDN string `yaml:"proxy_fqdn" json:"proxy_fqdn"`
23+
SecretKey string `yaml:"secret_key" json:"secret_key"`
24+
LogLevel string `yaml:"log_level" json:"log_level"`
25+
Proxies map[string]ProxyItem `yaml:"proxies" json:"proxies"`
2526
}
2627

27-
func LoadConfig() (Config, error) {
28-
configPath := GetEnvDefault("CONFIG_PATH", "./config.yaml")
29-
28+
func LoadConfig(configPath string) (Config, error) {
3029
var config Config
30+
31+
if configPath == "" {
32+
configPath = GetEnvDefault("CONFIG_PATH", "./config.yaml")
33+
}
34+
35+
logger.Info("loading configuration", "file", configPath)
3136
data, err := os.ReadFile(configPath)
3237
if err != nil {
3338
return config, err
@@ -37,6 +42,9 @@ func LoadConfig() (Config, error) {
3742
return config, err
3843
}
3944

45+
if config.LogLevel != "" {
46+
setLogLevel(config.LogLevel)
47+
}
4048
if config.ListenAddr == "" {
4149
config.ListenAddr = GetEnvDefault("LISTEN_ADDR", "0.0.0.0")
4250
}
@@ -58,10 +66,11 @@ func (cfg Config) Log() {
5866
// log the fully-parsed config data
5967
configJSON, err := json.MarshalIndent(cfg, "", " ")
6068
if err != nil {
61-
log.Fatalf("problem printing config: +%v", err)
69+
logger.Error("problem printing config", "error", err)
70+
return
6271
}
63-
log.Printf("starting up with the following configuration:\n%s", configJSON)
64-
72+
logger.Info("printing running configuration")
73+
fmt.Println(string(configJSON))
6574
}
6675

6776
// BestMatch searches through the configured proxies and tries to find the best

discovery.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"fmt"
5-
"log"
65
"net/http"
76
)
87

@@ -22,9 +21,9 @@ func ServeServiceDiscoveryEndpoint(w http.ResponseWriter, r *http.Request) {
2221
// for example with docker hub the result is "https://auth.docker.io/token"
2322
func DiscoverTokenEndpoint(registryHost string) (*WWWAuthenticateData, error) {
2423
url := fmt.Sprintf("https://%s/v2/", registryHost)
25-
log.Printf("DiscoverTokenEndpoint: making request to %s", url)
24+
logger.Debug("DiscoverTokenEndpoint: making request", "url", url)
2625
resp, err := http.Get(url)
27-
LogResponse("DiscoverTokenEndpoint: received the following response", resp)
26+
LogResponse("DiscoverTokenEndpoint: received response", resp)
2827
if err != nil {
2928
return nil, fmt.Errorf("DiscoverTokenEndpoint: failed to query the registry host %s: %+v", registryHost, err)
3029
}
@@ -38,7 +37,9 @@ func DiscoverTokenEndpoint(registryHost string) (*WWWAuthenticateData, error) {
3837
return nil, fmt.Errorf("DiscoverTokenEndpoint: www-authenticate header could not be parsed; header: %s", authHeader)
3938
}
4039

41-
log.Printf("DiscoverTokenEndpoint: DEBUG: parsed www-authenticate header %+v", authHeaderFields)
42-
log.Printf("DiscoverTokenEndpoint: registry %s: discovered token endpoint at %s", registryHost, authHeaderFields.Realm)
40+
logger.Debug("DiscoverTokenEndpoint: DEBUG: parsed www-authenticate header", "header", authHeaderFields)
41+
logger.Info("DiscoverTokenEndpoint: discovered endpoint",
42+
"registry", registryHost,
43+
"endpoint", authHeaderFields.Realm)
4344
return &authHeaderFields, nil
4445
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ go 1.22
44

55
require (
66
aidanwoods.dev/go-paseto v1.5.1
7+
github.com/urfave/cli/v2 v2.27.2
78
gopkg.in/yaml.v2 v2.4.0
89
)
910

1011
require (
1112
aidanwoods.dev/go-result v0.1.0 // indirect
13+
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
14+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
15+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
1216
golang.org/x/crypto v0.22.0 // indirect
13-
golang.org/x/sys v0.19.0 // indirect
17+
golang.org/x/sys v0.20.0 // indirect
1418
)

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ aidanwoods.dev/go-paseto v1.5.1 h1:IvT7wk7jmeTff6wyk7RlS6uAjUIAKU4MU2hkqr95lCo=
22
aidanwoods.dev/go-paseto v1.5.1/go.mod h1:9J13iCMdWrkfK1AxAg9QDHLaDMYSEP1ldbFiR+DfmVc=
33
aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8=
44
aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM=
5+
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
6+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
57
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
68
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
79
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
810
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
11+
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
12+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
913
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
1014
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15+
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
16+
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
17+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
18+
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
1119
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
1220
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
13-
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
14-
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
21+
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
22+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1523
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1624
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1725
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

main.go

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,39 +19,99 @@ package main
1919
import (
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-
3031
const (
31-
ctxKeyOriginalHost = originalHostContextKey("original-host")
3232
proxyConfigHeader string = "X-Proxy-Config"
3333
tokenKeyUpstreamToken string = "upstream-token"
3434
)
3535

3636
var (
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+
4056
func 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
}

middleware.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)