Skip to content

Commit 08c59de

Browse files
committed
Refactor webhook server to webhook api
Due external-dns API isn't so strict about request validation, drop respective tests Signed-off-by: Dinar Valeev <[email protected]>
1 parent e8133c1 commit 08c59de

File tree

9 files changed

+67
-512
lines changed

9 files changed

+67
-512
lines changed

cmd/webhook/init/configuration/configuration.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import (
2727

2828
// Config struct for configuration environmental variables
2929
type Config struct {
30-
ServerHost string `env:"SERVER_HOST" envDefault:"0.0.0.0"`
30+
ServerHost string `env:"SERVER_HOST" envDefault:"127.0.0.1"`
3131
ServerPort int `env:"SERVER_PORT" envDefault:"8888"`
32+
HealthCheckPort int `env:"HEALTH_CHECK_PORT" envDefault:"8080"`
3233
ServerReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT"`
3334
ServerWriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT"`
3435
DomainFilter []string `env:"DOMAIN_FILTER" envDefault:""`

cmd/webhook/init/configuration/configuration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestInit(t *testing.T) {
3030

3131
cfg := Init()
3232

33-
assert.Equal(t, "0.0.0.0", cfg.ServerHost)
33+
assert.Equal(t, "127.0.0.1", cfg.ServerHost)
3434
assert.Equal(t, 8888, cfg.ServerPort)
3535
assert.Equal(t, []string(nil), cfg.DomainFilter)
3636
assert.Equal(t, []string(nil), cfg.ExcludeDomains)

cmd/webhook/init/server/server.go

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,66 +19,66 @@ Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
1919
*/
2020

2121
import (
22-
"context"
23-
"errors"
2422
"fmt"
23+
"log"
24+
"net"
2525
"net/http"
26-
"os"
27-
"os/signal"
28-
"syscall"
29-
"time"
30-
31-
"github.com/go-chi/chi/v5"
32-
33-
log "github.com/sirupsen/logrus"
3426

3527
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/configuration"
36-
37-
"github.com/AbsaOSS/external-dns-infoblox-webhook/pkg/webhook"
28+
"sigs.k8s.io/external-dns/provider"
29+
"sigs.k8s.io/external-dns/provider/webhook/api"
3830
)
3931

32+
type WebhookServer struct {
33+
Ready bool
34+
Channel chan struct{}
35+
}
36+
37+
func NewServer() *WebhookServer {
38+
return &WebhookServer{
39+
Ready: false,
40+
Channel: make(chan struct{}, 1),
41+
}
42+
}
43+
4044
// Init server initialization function
4145
// The server will respond to the following endpoints:
4246
// - / (GET): initialization, negotiates headers and returns the domain filter
4347
// - /records (GET): returns the current records
4448
// - /records (POST): applies the changes
4549
// - /adjustendpoints (POST): executes the AdjustEndpoints method
46-
func Init(config configuration.Config, p *webhook.Webhook) *http.Server {
47-
r := chi.NewRouter()
48-
r.Use(webhook.Health)
49-
r.Get("/", p.Negotiate)
50-
r.Get("/records", p.Records)
51-
r.Post("/records", p.ApplyChanges)
52-
r.Post("/adjustendpoints", p.AdjustEndpoints)
50+
func (wh *WebhookServer) Start(config configuration.Config, p provider.Provider) {
51+
api.StartHTTPApi(p, wh.Channel, 0, 0, fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort))
52+
}
5353

54-
srv := createHTTPServer(fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort), r, config.ServerReadTimeout, config.ServerWriteTimeout)
54+
func (wh *WebhookServer) StartHealth(config configuration.Config) {
5555
go func() {
56-
log.Infof("starting server on addr: '%s' ", srv.Addr)
57-
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
58-
log.Errorf("can't serve on addr: '%s', error: %v", srv.Addr, err)
56+
listenAddr := fmt.Sprintf("0.0.0.0:%d", config.HealthCheckPort)
57+
m := http.NewServeMux()
58+
m.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
59+
select {
60+
case <-wh.Channel:
61+
wh.Ready = true
62+
default:
63+
}
64+
if wh.Ready {
65+
w.WriteHeader(http.StatusOK)
66+
return
67+
}
68+
w.WriteHeader(http.StatusInternalServerError)
69+
})
70+
s := &http.Server{
71+
Addr: listenAddr,
72+
Handler: m,
5973
}
60-
}()
61-
return srv
62-
}
63-
64-
func createHTTPServer(addr string, hand http.Handler, readTimeout, writeTimeout time.Duration) *http.Server {
65-
return &http.Server{
66-
ReadTimeout: readTimeout,
67-
WriteTimeout: writeTimeout,
68-
Addr: addr,
69-
Handler: hand,
70-
}
71-
}
7274

73-
// ShutdownGracefully gracefully shutdown the http server
74-
func ShutdownGracefully(srv *http.Server) {
75-
sigCh := make(chan os.Signal, 1)
76-
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
77-
sig := <-sigCh
78-
log.Infof("shutting down server due to received signal: %v", sig)
79-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
80-
if err := srv.Shutdown(ctx); err != nil {
81-
log.Errorf("error shutting down server: %v", err)
82-
}
83-
cancel()
75+
l, err := net.Listen("tcp", listenAddr)
76+
if err != nil {
77+
log.Fatal(err)
78+
}
79+
err = s.Serve(l)
80+
if err != nil {
81+
log.Fatalf("health listener stopped : %s", err)
82+
}
83+
}()
8484
}

cmd/webhook/init/server/server_test.go

Lines changed: 14 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"time"
3030

3131
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/configuration"
32-
"github.com/AbsaOSS/external-dns-infoblox-webhook/pkg/webhook"
3332
log "github.com/sirupsen/logrus"
3433
"sigs.k8s.io/external-dns/endpoint"
3534
"sigs.k8s.io/external-dns/plan"
@@ -58,15 +57,16 @@ var mockProvider *MockProvider
5857
func TestMain(m *testing.M) {
5958
mockProvider = &MockProvider{}
6059

61-
srv := Init(configuration.Init(), webhook.New(mockProvider))
62-
go ShutdownGracefully(srv)
60+
go func() {
61+
srv := NewServer()
62+
srv.StartHealth(configuration.Init())
63+
srv.Start(configuration.Init(), mockProvider)
64+
65+
}()
6366

6467
time.Sleep(300 * time.Millisecond)
6568

6669
m.Run()
67-
if err := srv.Shutdown(context.TODO()); err != nil {
68-
panic(err)
69-
}
7070
}
7171

7272
func TestRecords(t *testing.T) {
@@ -94,30 +94,6 @@ func TestRecords(t *testing.T) {
9494
},
9595
expectedBody: "[{\"dnsName\":\"test.example.com\",\"targets\":[\"\"],\"recordType\":\"A\",\"recordTTL\":3600,\"labels\":{\"label1\":\"value1\"}}]",
9696
},
97-
{
98-
name: "no accept header",
99-
method: http.MethodGet,
100-
headers: map[string]string{},
101-
path: "/records",
102-
body: "",
103-
expectedStatusCode: http.StatusNotAcceptable,
104-
expectedResponseHeaders: map[string]string{
105-
"Content-Type": "text/plain",
106-
},
107-
expectedBody: "client must provide an accept header",
108-
},
109-
{
110-
name: "wrong accept header",
111-
method: http.MethodGet,
112-
headers: map[string]string{"Accept": "invalid"},
113-
path: "/records",
114-
body: "",
115-
expectedStatusCode: http.StatusUnsupportedMediaType,
116-
expectedResponseHeaders: map[string]string{
117-
"Content-Type": "text/plain",
118-
},
119-
expectedBody: "Client must provide a valid versioned media type in the accept header: Unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
120-
},
12197
{
12298
name: "backend error",
12399
hasError: fmt.Errorf("backend error"),
@@ -238,46 +214,6 @@ func TestApplyChanges(t *testing.T) {
238214
},
239215
},
240216
},
241-
{
242-
name: "no content type header",
243-
method: http.MethodPost,
244-
path: "/records",
245-
body: "",
246-
expectedStatusCode: http.StatusNotAcceptable,
247-
expectedResponseHeaders: map[string]string{
248-
"Content-Type": "text/plain",
249-
},
250-
expectedBody: "client must provide a content type",
251-
},
252-
{
253-
name: "wrong content type header",
254-
method: http.MethodPost,
255-
headers: map[string]string{
256-
"Content-Type": "invalid",
257-
},
258-
path: "/records",
259-
body: "",
260-
expectedStatusCode: http.StatusUnsupportedMediaType,
261-
expectedResponseHeaders: map[string]string{
262-
"Content-Type": "text/plain",
263-
},
264-
expectedBody: "Client must provide a valid versioned media type in the content type: Unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
265-
},
266-
{
267-
name: "invalid json",
268-
method: http.MethodPost,
269-
headers: map[string]string{
270-
"Content-Type": "application/external.dns.webhook+json;version=1",
271-
"Accept": "application/external.dns.webhook+json;version=1",
272-
},
273-
path: "/records",
274-
body: "invalid",
275-
expectedStatusCode: http.StatusBadRequest,
276-
expectedResponseHeaders: map[string]string{
277-
"Content-Type": "text/plain",
278-
},
279-
expectedBody: "error decoding changes: invalid character 'i' looking for beginning of value",
280-
},
281217
{
282218
name: "backend error",
283219
hasError: fmt.Errorf("backend error"),
@@ -361,78 +297,18 @@ func TestAdjustEndpoints(t *testing.T) {
361297
},
362298
},
363299
},
364-
{
365-
name: "no content type header",
366-
method: http.MethodPost,
367-
headers: map[string]string{
368-
"Accept": "application/external.dns.webhook+json;version=1",
369-
},
370-
path: "/adjustendpoints",
371-
body: "",
372-
expectedStatusCode: http.StatusNotAcceptable,
373-
expectedResponseHeaders: map[string]string{
374-
"Content-Type": "text/plain",
375-
},
376-
expectedBody: "client must provide a content type",
377-
},
378-
{
379-
name: "wrong content type header",
380-
method: http.MethodPost,
381-
headers: map[string]string{
382-
"Content-Type": "invalid",
383-
"Accept": "application/external.dns.webhook+json;version=1",
384-
},
385-
path: "/adjustendpoints",
386-
body: "",
387-
expectedStatusCode: http.StatusUnsupportedMediaType,
388-
expectedResponseHeaders: map[string]string{
389-
"Content-Type": "text/plain",
390-
},
391-
expectedBody: "Client must provide a valid versioned media type in the content type: Unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
392-
},
393-
{
394-
name: "no accept header",
395-
method: http.MethodPost,
396-
headers: map[string]string{
397-
"Content-Type": "application/external.dns.webhook+json;version=1",
398-
},
399-
path: "/adjustendpoints",
400-
body: "",
401-
expectedStatusCode: http.StatusNotAcceptable,
402-
expectedResponseHeaders: map[string]string{
403-
"Content-Type": "text/plain",
404-
},
405-
expectedBody: "client must provide an accept header",
406-
},
407-
{
408-
name: "wrong accept header",
409-
method: http.MethodPost,
410-
headers: map[string]string{
411-
"Content-Type": "application/external.dns.webhook+json;version=1",
412-
"Accept": "invalid",
413-
},
414-
path: "/adjustendpoints",
415-
body: "",
416-
expectedStatusCode: http.StatusUnsupportedMediaType,
417-
expectedResponseHeaders: map[string]string{
418-
"Content-Type": "text/plain",
419-
},
420-
expectedBody: "Client must provide a valid versioned media type in the accept header: Unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
421-
},
422300
{
423301
name: "invalid json",
424302
method: http.MethodPost,
425303
headers: map[string]string{
426304
"Content-Type": "application/external.dns.webhook+json;version=1",
427305
"Accept": "application/external.dns.webhook+json;version=1",
428306
},
429-
path: "/adjustendpoints",
430-
body: "invalid",
431-
expectedStatusCode: http.StatusBadRequest,
432-
expectedResponseHeaders: map[string]string{
433-
"Content-Type": "text/plain",
434-
},
435-
expectedBody: "failed to decode request body: invalid character 'i' looking for beginning of value",
307+
path: "/adjustendpoints",
308+
body: "invalid",
309+
expectedStatusCode: http.StatusBadRequest,
310+
expectedResponseHeaders: map[string]string{},
311+
expectedBody: "",
436312
},
437313
}
438314

@@ -460,23 +336,11 @@ func TestNegotiate(t *testing.T) {
460336
headers: map[string]string{},
461337
path: "/",
462338
body: "",
463-
expectedStatusCode: http.StatusNotAcceptable,
464-
expectedResponseHeaders: map[string]string{
465-
"Content-Type": "text/plain",
466-
},
467-
expectedBody: "client must provide an accept header",
468-
},
469-
{
470-
name: "wrong accept header",
471-
method: http.MethodGet,
472-
headers: map[string]string{"Accept": "invalid"},
473-
path: "/",
474-
body: "",
475-
expectedStatusCode: http.StatusUnsupportedMediaType,
339+
expectedStatusCode: http.StatusOK,
476340
expectedResponseHeaders: map[string]string{
477-
"Content-Type": "text/plain",
341+
"Content-Type": "application/external.dns.webhook+json;version=1",
478342
},
479-
expectedBody: "Client must provide a valid versioned media type in the accept header: Unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
343+
expectedBody: "{}",
480344
},
481345
}
482346

cmd/webhook/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/dnsprovider"
2727
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/logging"
2828
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/server"
29-
"github.com/AbsaOSS/external-dns-infoblox-webhook/pkg/webhook"
3029
log "github.com/sirupsen/logrus"
3130
)
3231

@@ -59,6 +58,8 @@ func main() {
5958
log.Fatalf("failed to initialize provider: %v", err)
6059
}
6160

62-
srv := server.Init(config, webhook.New(provider))
63-
server.ShutdownGracefully(srv)
61+
srv := server.NewServer()
62+
63+
srv.StartHealth(config)
64+
srv.Start(config, provider)
6465
}

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ go 1.22.2
44

55
require (
66
github.com/caarlos0/env/v11 v11.0.0
7-
github.com/go-chi/chi/v5 v5.0.12
87
github.com/infobloxopen/infoblox-go-client/v2 v2.6.0
98
github.com/miekg/dns v1.1.59
109
github.com/sirupsen/logrus v1.9.3

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
77
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
99
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10-
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
11-
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
1210
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
1311
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
1412
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=

0 commit comments

Comments
 (0)