Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/webhook/init/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ import (

// Config struct for configuration environmental variables
type Config struct {
ServerHost string `env:"SERVER_HOST" envDefault:"0.0.0.0"`
ServerHost string `env:"SERVER_HOST" envDefault:"127.0.0.1"`
ServerPort int `env:"SERVER_PORT" envDefault:"8888"`
HealthCheckPort int `env:"HEALTH_CHECK_PORT" envDefault:"8080"`
ServerReadTimeout time.Duration `env:"SERVER_READ_TIMEOUT"`
ServerWriteTimeout time.Duration `env:"SERVER_WRITE_TIMEOUT"`
DomainFilter []string `env:"DOMAIN_FILTER" envDefault:""`
Expand Down
2 changes: 1 addition & 1 deletion cmd/webhook/init/configuration/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestInit(t *testing.T) {

cfg := Init()

assert.Equal(t, "0.0.0.0", cfg.ServerHost)
assert.Equal(t, "127.0.0.1", cfg.ServerHost)
assert.Equal(t, 8888, cfg.ServerPort)
assert.Equal(t, []string(nil), cfg.DomainFilter)
assert.Equal(t, []string(nil), cfg.ExcludeDomains)
Expand Down
92 changes: 46 additions & 46 deletions cmd/webhook/init/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,66 @@ Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/go-chi/chi/v5"

log "github.com/sirupsen/logrus"

"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/configuration"

"github.com/AbsaOSS/external-dns-infoblox-webhook/pkg/webhook"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/webhook/api"
)

type WebhookServer struct {
Ready bool
Channel chan struct{}
}

func NewServer() *WebhookServer {
return &WebhookServer{
Ready: false,
Channel: make(chan struct{}, 1),
}
}

// Init server initialization function
// The server will respond to the following endpoints:
// - / (GET): initialization, negotiates headers and returns the domain filter
// - /records (GET): returns the current records
// - /records (POST): applies the changes
// - /adjustendpoints (POST): executes the AdjustEndpoints method
func Init(config configuration.Config, p *webhook.Webhook) *http.Server {
r := chi.NewRouter()
r.Use(webhook.Health)
r.Get("/", p.Negotiate)
r.Get("/records", p.Records)
r.Post("/records", p.ApplyChanges)
r.Post("/adjustendpoints", p.AdjustEndpoints)
func (wh *WebhookServer) Start(config configuration.Config, p provider.Provider) {
api.StartHTTPApi(p, wh.Channel, 0, 0, fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort))
}

srv := createHTTPServer(fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort), r, config.ServerReadTimeout, config.ServerWriteTimeout)
func (wh *WebhookServer) StartHealth(config configuration.Config) {
go func() {
log.Infof("starting server on addr: '%s' ", srv.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Errorf("can't serve on addr: '%s', error: %v", srv.Addr, err)
listenAddr := fmt.Sprintf("0.0.0.0:%d", config.HealthCheckPort)
m := http.NewServeMux()
m.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
select {
case <-wh.Channel:
wh.Ready = true
default:
}
if wh.Ready {
w.WriteHeader(http.StatusOK)
return
}
w.WriteHeader(http.StatusInternalServerError)
})
s := &http.Server{
Addr: listenAddr,
Handler: m,
}
}()
return srv
}

func createHTTPServer(addr string, hand http.Handler, readTimeout, writeTimeout time.Duration) *http.Server {
return &http.Server{
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
Addr: addr,
Handler: hand,
}
}

// ShutdownGracefully gracefully shutdown the http server
func ShutdownGracefully(srv *http.Server) {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
sig := <-sigCh
log.Infof("shutting down server due to received signal: %v", sig)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
if err := srv.Shutdown(ctx); err != nil {
log.Errorf("error shutting down server: %v", err)
}
cancel()
l, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(err)
}
err = s.Serve(l)
if err != nil {
log.Fatalf("health listener stopped : %s", err)
}
}()
}
164 changes: 14 additions & 150 deletions cmd/webhook/init/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"time"

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

srv := Init(configuration.Init(), webhook.New(mockProvider))
go ShutdownGracefully(srv)
go func() {
srv := NewServer()
srv.StartHealth(configuration.Init())
srv.Start(configuration.Init(), mockProvider)

}()

time.Sleep(300 * time.Millisecond)

m.Run()
if err := srv.Shutdown(context.TODO()); err != nil {
panic(err)
}
}

func TestRecords(t *testing.T) {
Expand Down Expand Up @@ -94,30 +94,6 @@ func TestRecords(t *testing.T) {
},
expectedBody: "[{\"dnsName\":\"test.example.com\",\"targets\":[\"\"],\"recordType\":\"A\",\"recordTTL\":3600,\"labels\":{\"label1\":\"value1\"}}]",
},
{
name: "no accept header",
method: http.MethodGet,
headers: map[string]string{},
path: "/records",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide an accept header",
},
{
name: "wrong accept header",
method: http.MethodGet,
headers: map[string]string{"Accept": "invalid"},
path: "/records",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
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'",
},
{
name: "backend error",
hasError: fmt.Errorf("backend error"),
Expand Down Expand Up @@ -238,46 +214,6 @@ func TestApplyChanges(t *testing.T) {
},
},
},
{
name: "no content type header",
method: http.MethodPost,
path: "/records",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide a content type",
},
{
name: "wrong content type header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "invalid",
},
path: "/records",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
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'",
},
{
name: "invalid json",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/records",
body: "invalid",
expectedStatusCode: http.StatusBadRequest,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "error decoding changes: invalid character 'i' looking for beginning of value",
},
{
name: "backend error",
hasError: fmt.Errorf("backend error"),
Expand Down Expand Up @@ -361,78 +297,18 @@ func TestAdjustEndpoints(t *testing.T) {
},
},
},
{
name: "no content type header",
method: http.MethodPost,
headers: map[string]string{
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/adjustendpoints",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide a content type",
},
{
name: "wrong content type header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "invalid",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/adjustendpoints",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
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'",
},
{
name: "no accept header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
},
path: "/adjustendpoints",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide an accept header",
},
{
name: "wrong accept header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "invalid",
},
path: "/adjustendpoints",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
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'",
},
{
name: "invalid json",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/adjustendpoints",
body: "invalid",
expectedStatusCode: http.StatusBadRequest,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "failed to decode request body: invalid character 'i' looking for beginning of value",
path: "/adjustendpoints",
body: "invalid",
expectedStatusCode: http.StatusBadRequest,
expectedResponseHeaders: map[string]string{},
expectedBody: "",
},
}

Expand Down Expand Up @@ -460,23 +336,11 @@ func TestNegotiate(t *testing.T) {
headers: map[string]string{},
path: "/",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide an accept header",
},
{
name: "wrong accept header",
method: http.MethodGet,
headers: map[string]string{"Accept": "invalid"},
path: "/",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedStatusCode: http.StatusOK,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
"Content-Type": "application/external.dns.webhook+json;version=1",
},
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'",
expectedBody: "{}",
},
}

Expand Down
7 changes: 4 additions & 3 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/dnsprovider"
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/logging"
"github.com/AbsaOSS/external-dns-infoblox-webhook/cmd/webhook/init/server"
"github.com/AbsaOSS/external-dns-infoblox-webhook/pkg/webhook"
log "github.com/sirupsen/logrus"
)

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

srv := server.Init(config, webhook.New(provider))
server.ShutdownGracefully(srv)
srv := server.NewServer()

srv.StartHealth(config)
srv.Start(config, provider)
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.22.2

require (
github.com/caarlos0/env/v11 v11.0.0
github.com/go-chi/chi/v5 v5.0.12
github.com/infobloxopen/infoblox-go-client/v2 v2.6.0
github.com/miekg/dns v1.1.59
github.com/sirupsen/logrus v1.9.3
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
Expand Down
Loading
Loading