Skip to content

Commit d4b9d30

Browse files
committed
httptransport: rework constructor
This makes the `New` function return a `http.Handler` instead of a custom Server type. Signed-off-by: Hank Donnay <[email protected]>
1 parent 067bf86 commit d4b9d30

File tree

3 files changed

+66
-178
lines changed

3 files changed

+66
-178
lines changed

cmd/clair/main.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ func main() {
126126
if err != nil {
127127
return fmt.Errorf("service initialization failed: %w", err)
128128
}
129-
h, err := httptransport.New(srvctx, conf, srvs.Indexer, srvs.Matcher, srvs.Notifier)
129+
srv := http.Server{
130+
BaseContext: func(_ net.Listener) context.Context { return srvctx },
131+
}
132+
srv.Handler, err = httptransport.New(srvctx, &conf, srvs.Indexer, srvs.Matcher, srvs.Notifier)
130133
if err != nil {
131134
return fmt.Errorf("http transport configuration failed: %w", err)
132135
}
@@ -140,11 +143,12 @@ func main() {
140143
return fmt.Errorf("tls configuration failed: %w", err)
141144
}
142145
cfg.NextProtos = []string{"h2"}
146+
srv.TLSConfig = cfg
143147
l = tls.NewListener(l, cfg)
144148
}
145-
down.Add(h.Server)
149+
down.Add(&srv)
146150
health.Ready()
147-
if err := h.Serve(l); err != http.ErrServerClosed {
151+
if err := srv.Serve(l); err != http.ErrServerClosed {
148152
return fmt.Errorf("http transport failed to launch: %w", err)
149153
}
150154
return nil

httptransport/server.go

Lines changed: 55 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,15 @@ package httptransport
33
import (
44
"context"
55
"fmt"
6-
"net"
76
"net/http"
87
"time"
98

109
"github.com/quay/clair/config"
1110
"github.com/quay/zlog"
12-
othttp "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
11+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
1312
"go.opentelemetry.io/otel"
1413
"golang.org/x/sync/semaphore"
1514

16-
clairerror "github.com/quay/clair/v4/clair-error"
1715
"github.com/quay/clair/v4/indexer"
1816
"github.com/quay/clair/v4/matcher"
1917
"github.com/quay/clair/v4/notifier"
@@ -39,193 +37,85 @@ const (
3937
OpenAPIV1Path = "/openapi/v1"
4038
)
4139

42-
// Server is the primary http server Clair exposes its functionality on.
43-
type Server struct {
44-
// Server embeds a http.Server and http.ServeMux.
45-
// The http.Server will be configured with the ServeMux on successful
46-
// initialization.
47-
conf config.Config
48-
*http.Server
49-
*http.ServeMux
50-
indexer indexer.Service
51-
matcher matcher.Service
52-
notifier notifier.Service
53-
traceOpt othttp.Option
54-
}
55-
56-
func New(ctx context.Context, conf config.Config, indexer indexer.Service, matcher matcher.Service, notifier notifier.Service) (*Server, error) {
57-
serv := &http.Server{
58-
Addr: conf.HTTPListenAddr,
59-
// use the passed in global context as the base context for all http
60-
// requests handled by this server
61-
BaseContext: func(net.Listener) context.Context { return ctx },
62-
}
40+
func New(ctx context.Context, conf *config.Config, indexer indexer.Service, matcher matcher.Service, notifier notifier.Service) (http.Handler, error) {
6341
mux := http.NewServeMux()
64-
t := &Server{
65-
conf: conf,
66-
Server: serv,
67-
ServeMux: mux,
68-
indexer: indexer,
69-
matcher: matcher,
70-
notifier: notifier,
71-
traceOpt: othttp.WithTracerProvider(otel.GetTracerProvider()),
72-
}
42+
traceOpt := otelhttp.WithTracerProvider(otel.GetTracerProvider())
7343
ctx = zlog.ContextWithValues(ctx, "component", "httptransport/New")
7444

75-
mux.Handle(OpenAPIV1Path, DiscoveryHandler(ctx, OpenAPIV1Path, t.traceOpt))
45+
mux.Handle(OpenAPIV1Path, DiscoveryHandler(ctx, OpenAPIV1Path, traceOpt))
7646
zlog.Info(ctx).Str("path", OpenAPIV1Path).Msg("openapi discovery configured")
7747

78-
var e error
79-
switch conf.Mode {
80-
case config.ComboMode:
81-
e = t.configureComboMode(ctx)
82-
if e != nil {
83-
return nil, e
48+
// NOTE(hank) My brain always wants to rewrite constructions like the
49+
// following as a switch, but this is actually cleaner as an "if" sequence.
50+
51+
if conf.Mode == config.IndexerMode || conf.Mode == config.ComboMode {
52+
if indexer == nil {
53+
return nil, fmt.Errorf("mode %q requires an indexer service", conf.Mode)
54+
}
55+
prefix := indexerRoot + apiRoot
56+
57+
v1, err := NewIndexerV1(ctx, prefix, indexer, traceOpt)
58+
if err != nil {
59+
return nil, fmt.Errorf("indexer configuration: %w", err)
8460
}
85-
case config.IndexerMode:
86-
e = t.configureIndexerMode(ctx)
87-
if e != nil {
88-
return nil, e
61+
var sem *semaphore.Weighted
62+
if ct := conf.Indexer.IndexReportRequestConcurrency; ct > 0 {
63+
sem = semaphore.NewWeighted(int64(ct))
8964
}
90-
case config.MatcherMode:
91-
e = t.configureMatcherMode(ctx)
92-
if e != nil {
93-
return nil, e
65+
rl := &limitHandler{
66+
Check: func(r *http.Request) (*semaphore.Weighted, string) {
67+
if r.Method != http.MethodPost && r.URL.Path != IndexAPIPath {
68+
return nil, ""
69+
}
70+
// Nil if the relevant config option isn't set.
71+
return sem, IndexAPIPath
72+
},
73+
Next: v1,
9474
}
95-
case config.NotifierMode:
96-
e = t.configureNotifierMode(ctx)
97-
if e != nil {
98-
return nil, e
75+
mux.Handle(prefix, rl)
76+
}
77+
if conf.Mode == config.MatcherMode || conf.Mode == config.ComboMode {
78+
if indexer == nil || matcher == nil {
79+
return nil, fmt.Errorf("mode %q requires both an indexer service and a matcher service", conf.Mode)
9980
}
81+
prefix := matcherRoot + apiRoot
82+
v1 := NewMatcherV1(ctx, prefix, matcher, indexer, time.Duration(conf.Matcher.CacheAge), traceOpt)
83+
mux.Handle(prefix, v1)
10084
}
101-
102-
// attach HttpTransport to server, this works because we embed http.ServeMux
103-
t.Server.Handler = t
104-
85+
if conf.Mode == config.NotifierMode || (conf.Mode == config.ComboMode && notifier != nil) {
86+
if notifier == nil {
87+
return nil, fmt.Errorf("mode %q requires a notifier service", conf.Mode)
88+
}
89+
prefix := notifierRoot + apiRoot
90+
v1, err := NewNotificationV1(ctx, prefix, notifier, traceOpt)
91+
if err != nil {
92+
return nil, fmt.Errorf("notifier configuration: %w", err)
93+
}
94+
mux.Handle(prefix, v1)
95+
}
96+
if conf.Mode == config.ComboMode && notifier == nil {
97+
zlog.Debug(ctx).Msg("skipping unconfigured notifier")
98+
}
99+
var ret http.Handler = mux
105100
// Add endpoint authentication if configured to add auth. Must happen after
106101
// mux was configured for given mode.
107102
if conf.Auth.Any() {
108-
err := t.configureWithAuth(ctx)
103+
h, err := authHandler(conf, mux)
109104
if err != nil {
110105
zlog.Warn(ctx).
111106
Err(err).
112107
Msg("received error configuring auth middleware")
108+
return nil, err
113109
}
110+
ret = h
114111
}
115-
116-
return t, nil
117-
}
118-
119-
// ConfigureComboMode configures the HttpTransport for ComboMode.
120-
//
121-
// This mode runs both Indexer and Matcher in a single process.
122-
func (t *Server) configureComboMode(ctx context.Context) error {
123-
// requires both indexer and matcher services
124-
if t.indexer == nil || t.matcher == nil {
125-
return clairerror.ErrNotInitialized{Msg: "Combo mode requires both indexer and macher services"}
126-
}
127-
128-
err := t.configureIndexerMode(ctx)
129-
if err != nil {
130-
return clairerror.ErrNotInitialized{Msg: "could not configure indexer: " + err.Error()}
131-
}
132-
133-
err = t.configureMatcherMode(ctx)
134-
if err != nil {
135-
return clairerror.ErrNotInitialized{Msg: "could not configure matcher: " + err.Error()}
136-
}
137-
138-
if t.notifier != nil {
139-
if err := t.configureNotifierMode(ctx); err != nil {
140-
return clairerror.ErrNotInitialized{Msg: "could not configure notifier: " + err.Error()}
141-
}
142-
}
143-
144-
return nil
145-
}
146-
147-
// ConfigureIndexerMode configures the HttpTransport for IndexerMode.
148-
//
149-
// This mode runs only an Indexer in a single process.
150-
func (t *Server) configureIndexerMode(ctx context.Context) error {
151-
// requires only indexer service
152-
if t.indexer == nil {
153-
return clairerror.ErrNotInitialized{Msg: "IndexerMode requires an indexer service"}
154-
}
155-
prefix := indexerRoot + apiRoot
156-
157-
v1, err := NewIndexerV1(ctx, prefix, t.indexer, t.traceOpt)
158-
if err != nil {
159-
return fmt.Errorf("indexer configuration: %w", err)
160-
}
161-
var sem *semaphore.Weighted
162-
if ct := t.conf.Indexer.IndexReportRequestConcurrency; ct > 0 {
163-
sem = semaphore.NewWeighted(int64(ct))
164-
}
165-
rl := &limitHandler{
166-
Check: func(r *http.Request) (*semaphore.Weighted, string) {
167-
if r.Method != http.MethodPost && r.URL.Path != IndexAPIPath {
168-
return nil, ""
169-
}
170-
// Nil if the relevant config option isn't set.
171-
return sem, IndexAPIPath
172-
},
173-
Next: v1,
174-
}
175-
t.Handle(prefix, rl)
176-
return nil
177-
}
178-
179-
// ConfigureMatcherMode configures HttpTransport for MatcherMode.
180-
//
181-
// This mode runs only a Matcher in a single process.
182-
func (t *Server) configureMatcherMode(ctx context.Context) error {
183-
// requires both an indexer and matcher service. indexer service
184-
// is assumed to be a remote call over the network
185-
if t.indexer == nil || t.matcher == nil {
186-
return clairerror.ErrNotInitialized{Msg: "MatcherMode requires both indexer and matcher services"}
187-
}
188-
prefix := matcherRoot + apiRoot
189-
v1 := NewMatcherV1(ctx, prefix, t.matcher, t.indexer, time.Duration(t.conf.Matcher.CacheAge), t.traceOpt)
190-
191-
t.Handle(prefix, v1)
192-
return nil
193-
}
194-
195-
// ConfigureNotifierMode configures HttpTransport for NotifierMode.
196-
//
197-
// This mode runs only a Notifier in a single process.
198-
func (t *Server) configureNotifierMode(ctx context.Context) error {
199-
if t.notifier == nil {
200-
return clairerror.ErrNotInitialized{Msg: "NotifierMode requires a notifier service"}
201-
}
202-
prefix := notifierRoot + apiRoot
203-
v1, err := NewNotificationV1(ctx, prefix, t.notifier, t.traceOpt)
204-
if err != nil {
205-
return fmt.Errorf("notifier configuration: %w", err)
206-
}
207-
208-
t.Handle(prefix, v1)
209-
return nil
112+
return ret, nil
210113
}
211114

212115
// IntraserviceIssuer is the issuer that will be used if Clair is configured to
213116
// mint its own JWTs.
214117
const IntraserviceIssuer = `clair-intraservice`
215118

216-
// ConfigureWithAuth will take the current serve mux and wrap it in an Auth
217-
// middleware handler.
218-
//
219-
// Must be ran after the config*Mode method of choice.
220-
func (t *Server) configureWithAuth(_ context.Context) error {
221-
h, err := authHandler(&t.conf, t.Server.Handler)
222-
if err != nil {
223-
return err
224-
}
225-
t.Server.Handler = h
226-
return nil
227-
}
228-
229119
// Unmodified determines whether to return a conditional response.
230120
func unmodified(r *http.Request, v string) bool {
231121
if vs, ok := r.Header["If-None-Match"]; ok {

httptransport/server_test.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import (
1010
"testing"
1111

1212
"github.com/google/uuid"
13+
"github.com/quay/clair/config"
1314
"github.com/quay/claircore"
1415
"github.com/quay/claircore/libvuln/driver"
1516
"github.com/quay/zlog"
16-
othttp "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
17-
"go.opentelemetry.io/otel"
1817

1918
"github.com/quay/clair/v4/indexer"
2019
"github.com/quay/clair/v4/matcher"
@@ -45,17 +44,12 @@ func TestUpdateEndpoints(t *testing.T) {
4544
return nil, nil
4645
},
4746
}
48-
s := &Server{
49-
matcher: m,
50-
indexer: i,
51-
ServeMux: http.NewServeMux(),
52-
traceOpt: othttp.WithTracerProvider(otel.GetTracerProvider()),
53-
}
5447
ctx := zlog.Test(context.Background(), t)
55-
if err := s.configureMatcherMode(ctx); err != nil {
48+
h, err := New(ctx, &config.Config{Mode: config.MatcherMode}, i, m, nil)
49+
if err != nil {
5650
t.Error(err)
5751
}
58-
srv := httptest.NewUnstartedServer(s)
52+
srv := httptest.NewUnstartedServer(h)
5953
srv.Config.BaseContext = func(_ net.Listener) context.Context {
6054
return ctx
6155
}

0 commit comments

Comments
 (0)