Skip to content

Commit

Permalink
feat: support http client- and server
Browse files Browse the repository at this point in the history
  • Loading branch information
soerenschneider committed Dec 5, 2023
1 parent 6e0e05a commit 3862498
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 14 deletions.
25 changes: 16 additions & 9 deletions cmd/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,27 @@ func dieOnError(err error, msg string) {
func buildNotifiers(config *conf.ClientConf) (map[string]client.EventDispatch, error) {
dispatchers := map[string]client.EventDispatch{}

var errs, err error
var errs error
if len(config.Brokers) > 0 {
for _, broker := range config.Brokers {
dispatchers[broker], err = mqtt.NewMqttClient(broker, config.ClientId, fmt.Sprintf("dyndns/%s", config.Host), config.TlsConfig())
dispatcher, err := mqtt.NewMqttClient(broker, config.ClientId, fmt.Sprintf("dyndns/%s", config.Host), config.TlsConfig())
if err != nil {
errs = multierr.Append(errs, err)
} else {
dispatchers[broker] = dispatcher
}
}
}

if len(config.HttpConfig.Url) > 0 {
httpDispatcher, err := client.NewHttpDispatcher(config.HttpConfig.Url)
if err != nil {
errs = multierr.Append(errs, err)
if len(config.HttpConf) > 0 {
for _, dispatcher := range config.HttpConf {
httpDispatcher, err := client.NewHttpDispatcher(dispatcher.Url)
if err != nil {
errs = multierr.Append(errs, err)
} else {
dispatchers[dispatcher.Url] = httpDispatcher
}
}
dispatchers[config.HttpConfig.Url] = httpDispatcher
}

return dispatchers, errs
Expand All @@ -118,9 +123,11 @@ func RunClient(config *conf.ClientConf) {
dieOnError(err, "could not build ip resolver")

dispatchers, err := buildNotifiers(config)
dieOnError(err, "Could not build mqtt dispatcher")
if len(dispatchers) == 0 {
log.Fatal().Msg("no dispatchers defined")
log.Fatal().Err(err).Msg("no dispatchers built")
}
if err != nil {
log.Error().Err(err).Msg("could not build all dispatchers")
}

reconciler, err := client.NewReconciler(dispatchers)
Expand Down
23 changes: 21 additions & 2 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/soerenschneider/dyndns/conf"
"github.com/soerenschneider/dyndns/internal"
"github.com/soerenschneider/dyndns/internal/common"
"github.com/soerenschneider/dyndns/internal/events/http"
"github.com/soerenschneider/dyndns/internal/events/mqtt"
"github.com/soerenschneider/dyndns/internal/metrics"
"github.com/soerenschneider/dyndns/internal/notification"
Expand Down Expand Up @@ -80,8 +81,16 @@ func RunServer(config *conf.ServerConf) {
var servers []*mqtt.MqttBus
for _, broker := range config.Brokers {
mqttServer, err := mqtt.NewMqttServer(broker, config.ClientId, notificationTopic, config.TlsConfig(), requestsChannel)
dieOnError(err, "Could not build mqtt dispatcher")
servers = append(servers, mqttServer)
if err != nil {
log.Error().Err(err).Msg("could not connect to mqtt")
} else {
servers = append(servers, mqttServer)
}
}

log.Info().Msgf("Configured %d servers", len(servers))
if len(servers) == 0 {
log.Fatal().Err(err).Msg("not connected to a single mqtt server")
}

ctx, cancel := context.WithCancel(context.Background())
Expand All @@ -105,6 +114,7 @@ func RunServer(config *conf.ServerConf) {
dyndnsServer, err := server.NewServer(*config, propagator, requestsChannel, notificationImpl)
dieOnError(err, "could not build dyndns server")

log.Info().Msg("Ready, listening for incoming requests")
go dyndnsServer.Listen()

term := make(chan os.Signal, 1)
Expand Down Expand Up @@ -145,6 +155,15 @@ func buildVaultClient(conf *conf.VaultConfig) (*api.Client, error) {
return api.NewClient(config)
}

func buildHttpServer(conf *conf.ServerConf, req chan common.UpdateRecordRequest) (*http.HttpServer, error) {
http, err := http.New(conf.HttpServer.Addr, req)
if err != nil {
return nil, err
}

return http, nil
}

// buildCredentialProvider returns the vault credentials provider, but only if it succeeds to login at vault
// otherwise the default credentials provider by AWS is used, trying to be resilient
func buildCredentialProvider(config *conf.VaultConfig, client *api.Client, auth vaultDyndns.Auth) (credentials.Provider, error) {
Expand Down
8 changes: 5 additions & 3 deletions conf/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ type ClientConf struct {
NetworkInterface string `json:"interface,omitempty"`
Once bool // this is not parsed via json, it's an cli flag

HttpConfig struct {
Url string `json:"url"`
} `json:"http"`
HttpConf []HttpConfig `json:"http"`
MqttConfig
*EmailConfig `json:"notifications"`
}

type HttpConfig struct {
Url string `json:"url"`
}

func ReadClientConfig(path string) (*ClientConf, error) {
conf := getDefaultClientConfig()
if path == "" {
Expand Down
5 changes: 5 additions & 0 deletions conf/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ type ServerConf struct {
KnownHosts map[string][]string `json:"known_hosts" env:"DYNDNS_KNOWN_HOSTS" validate:"required"`
HostedZoneId string `json:"hosted_zone_id" env:"DYNDNS_HOSTED_ZONE_ID" validate:"required"`
MetricsListener string `json:"metrics_listen,omitempty" validate:"omitempty,tcp_addr"`
HttpServer struct {
Addr string `json:"http"`
TlsCert string `json:"tls_cert"`
TlsKey string `json:"tls_key"`
}
*MqttConfig
*VaultConfig
*EmailConfig `json:"notifications"`
Expand Down
109 changes: 109 additions & 0 deletions internal/events/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package http

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
"time"

"github.com/rs/zerolog/log"
"github.com/soerenschneider/dyndns/internal/common"
"go.uber.org/multierr"
)

type HttpServer struct {
address string
requests chan common.UpdateRecordRequest

// optional
certFile string
keyFile string
}

type WebhookOpts func(*HttpServer) error

func New(address string, requestsChan chan common.UpdateRecordRequest, opts ...WebhookOpts) (*HttpServer, error) {
if len(address) == 0 {
return nil, errors.New("empty address provided")
}

w := &HttpServer{
address: address,
}

var errs error
for _, opt := range opts {
if err := opt(w); err != nil {
errs = multierr.Append(errs, err)
}
}

return w, errs
}

func (s *HttpServer) IsTLSConfigured() bool {
return len(s.certFile) > 0 && len(s.keyFile) > 0
}

func (s *HttpServer) handle(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(400)
}

data, err := io.ReadAll(r.Body)
if err != nil {
return
}
defer r.Body.Close()

payload := common.UpdateRecordRequest{}
if err := json.Unmarshal(data, &payload); err != nil {
return
}

s.requests <- payload
w.WriteHeader(http.StatusOK)
}

func (s *HttpServer) Listen(ctx context.Context, events chan bool, wg *sync.WaitGroup) error {
wg.Add(1)

mux := http.NewServeMux()
mux.HandleFunc("/update", s.handle)

server := http.Server{
Addr: s.address,
Handler: mux,
ReadTimeout: 3 * time.Second,
ReadHeaderTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
IdleTimeout: 30 * time.Second,
}

errChan := make(chan error)
go func() {
if s.IsTLSConfigured() {
if err := server.ListenAndServeTLS(s.certFile, s.keyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
errChan <- fmt.Errorf("can not start webhook server: %w", err)
}
} else {
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errChan <- fmt.Errorf("can not start webhook server: %w", err)
}
}
}()

select {
case <-ctx.Done():
log.Info().Msg("Stopping webhook server")
err := server.Shutdown(ctx)
wg.Done()
return err
case err := <-errChan:
return err
}
}
21 changes: 21 additions & 0 deletions internal/events/http/http_opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package http

import (
"errors"
)

func WithTLS(certFile, keyFile string) func(s *HttpServer) error {
return func(s *HttpServer) error {
if len(certFile) == 0 {
return errors.New("empty certfile")
}

if len(keyFile) == 0 {
return errors.New("empty keyfile")
}

s.certFile = certFile
s.keyFile = keyFile
return nil
}
}

0 comments on commit 3862498

Please sign in to comment.