121 lines
3.1 KiB
Go
121 lines
3.1 KiB
Go
package host
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"slices"
|
|
"sync"
|
|
|
|
"git.wzray.com/homelab/mastermind/internal/config"
|
|
"git.wzray.com/homelab/mastermind/internal/state"
|
|
"git.wzray.com/homelab/mastermind/internal/types"
|
|
"git.wzray.com/homelab/mastermind/internal/web/client"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type Role struct {
|
|
state *state.RuntimeState
|
|
config config.HostConfig
|
|
|
|
client *traefikClient
|
|
tasksGroup sync.WaitGroup
|
|
|
|
externalDomains []string // TODO: i don't like hardcoding external/internal logic here
|
|
internalDomains []string
|
|
}
|
|
|
|
func New(state *state.RuntimeState, config config.HostConfig) *Role {
|
|
return &Role{
|
|
client: newClient(config.Domain, config.IpAddress),
|
|
state: state,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
func (r *Role) sendUpdate(domains []string, role types.Role) {
|
|
state := types.HostState{
|
|
Domains: domains,
|
|
Name: r.state.Self.Name,
|
|
Endpoint: r.state.Self.Address,
|
|
}
|
|
|
|
for _, node := range r.state.Registry.ByRole(role) {
|
|
r.tasksGroup.Go(func() {
|
|
logger := log.With().Str("name", node.Name).Logger()
|
|
logger.Debug().Msg("sending update")
|
|
if _, err := client.Post[any](node.Address, types.PathDnsCallback, state); err != nil {
|
|
logger.Warn().Err(err).Msg("unable to send dns info")
|
|
} else {
|
|
logger.Debug().Msg("update sent")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (r *Role) mutateState(resp traefikResponse) {
|
|
newInternal := resp.Domains(r.config.InternalEntrypoint)
|
|
newExternal := resp.Domains(r.config.ExternalEntrypoint)
|
|
|
|
if !slices.Equal(newInternal, r.internalDomains) {
|
|
log.Info().Msg("internal domains updated, propogating")
|
|
r.internalDomains = newInternal
|
|
r.sendUpdate(newInternal, types.DnsRole)
|
|
}
|
|
|
|
if !slices.Equal(newExternal, r.externalDomains) {
|
|
log.Info().Msg("internal domains updated, propogating")
|
|
r.externalDomains = newExternal
|
|
r.sendUpdate(newExternal, types.NameserverRole)
|
|
}
|
|
}
|
|
|
|
func (r *Role) onCallback(w http.ResponseWriter, req *http.Request) {
|
|
var resp traefikResponse
|
|
if err := json.NewDecoder(req.Body).Decode(&resp); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
log.Err(err).Msg("unable to decode traefik callback data")
|
|
return
|
|
}
|
|
|
|
r.mutateState(resp)
|
|
|
|
w.Write([]byte("OK"))
|
|
}
|
|
|
|
func (r *Role) getInternal() (types.HostState, error) {
|
|
return types.HostState{
|
|
Domains: r.internalDomains,
|
|
Endpoint: r.state.Self.Address,
|
|
Name: r.state.Self.Name,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Role) getExternal() (types.HostState, error) {
|
|
return types.HostState{}, nil
|
|
}
|
|
|
|
func (r *Role) RegisterHandlers(rg types.Registrator) {
|
|
rg.RegisterRaw(http.MethodPost, types.PathHostCallback.String(), r.onCallback)
|
|
rg.Register(types.GetEndpoint(types.PathHostDns, r.getInternal))
|
|
rg.Register(types.GetEndpoint(types.PathHostNs, r.getExternal))
|
|
}
|
|
|
|
func (r *Role) OnStartup(ctx context.Context) error {
|
|
resp, err := r.client.GetRawData()
|
|
if err != nil {
|
|
return fmt.Errorf("get traefik state: %w", err)
|
|
}
|
|
|
|
log.Info().Msg("got raw data from traefik")
|
|
log.Debug().Interface("response", resp).Send()
|
|
|
|
r.mutateState(*resp)
|
|
return nil
|
|
}
|
|
|
|
func (r *Role) OnShutdown() error {
|
|
r.tasksGroup.Wait()
|
|
return nil
|
|
}
|