Skip to content

Commit

Permalink
Merge pull request #24 from skoef/compatFlag
Browse files Browse the repository at this point in the history
add compatBird213 flag
  • Loading branch information
skoef authored Nov 6, 2024
2 parents 1076ba6 + 180579e commit d0f2bdc
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 169 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Configuration section for global options.
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| configfile | Path to configuration file that will be generated and should be included in the BIRD configuration. Defaults to **/etc/bird/birdwatcher.conf**. |
| reloadcommand | Command to invoke to signal BIRD the configuration should be reloaded. Defaults to **/usr/sbin/birdc configure**. |
| compatbird213 | To use birdwatcher with BIRD 2.13 or earlier, enable this flag. It will remove the function return types from the output |

## **[services]**

Expand Down
57 changes: 45 additions & 12 deletions birdwatcher/bird.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ package birdwatcher

import (
"bytes"
_ "embed"
"errors"
"net"
"os"
"text/template"
)

//go:embed templates/functions.tpl
var functionsTemplate string

// make sure prefixPad can be used in templates
var tplFuncs = template.FuncMap{
"prefixPad": prefixPad,
}

var errConfigIdentical = errors.New("configuration file is identical")

func updateBirdConfig(filename string, prefixes PrefixCollection) error {
func updateBirdConfig(config Config, prefixes PrefixCollection) error {
// write config to temp file
tmpFilename := filename + ".tmp"
tmpFilename := config.ConfigFile + ".tmp"
// make sure we don't keep tmp file around when something goes wrong
defer func(x string) {
if _, err := os.Stat(x); !os.IsNotExist(err) {
Expand All @@ -19,20 +30,20 @@ func updateBirdConfig(filename string, prefixes PrefixCollection) error {
}
}(tmpFilename)

if err := writeBirdConfig(tmpFilename, prefixes); err != nil {
if err := writeBirdConfig(tmpFilename, prefixes, config.CompatBird213); err != nil {
return err
}

// compare new file with original config file
if compareFiles(tmpFilename, filename) {
if compareFiles(tmpFilename, config.ConfigFile) {
return errConfigIdentical
}

// move tmp file to right place
return os.Rename(tmpFilename, filename)
return os.Rename(tmpFilename, config.ConfigFile)
}

func writeBirdConfig(filename string, prefixes PrefixCollection) error {
func writeBirdConfig(filename string, prefixes PrefixCollection, compatBird213 bool) error {
var err error

// open file
Expand All @@ -41,20 +52,42 @@ func writeBirdConfig(filename string, prefixes PrefixCollection) error {
return err
}

// prepare content with a header
output := "# DO NOT EDIT MANUALLY\n"
tmpl := template.Must(template.New("func").Funcs(tplFuncs).Parse(functionsTemplate))

// append marshalled prefixsets
for _, p := range prefixes {
output += p.Marshal()
tplBody := struct {
Collections PrefixCollection
CompatBird213 bool
}{
Collections: prefixes,
CompatBird213: compatBird213,
}

var buf bytes.Buffer
if err := tmpl.Execute(&buf, tplBody); err != nil {
return err
}

// write data to file
_, err = f.WriteString(output)
_, err = f.Write(buf.Bytes())

return err
}

// prefixPad is a helper function for the template
// basically returns CIDR notations per IPNet, each suffixed with a , except for
// the last entry
func prefixPad(x []net.IPNet) []string {
pp := make([]string, len(x))
for i, p := range x {
pp[i] = p.String()
if i < len(x)-1 {
pp[i] += ","
}
}

return pp
}

func compareFiles(fileA, fileB string) bool {
data, err := os.ReadFile(fileA)
if err != nil {
Expand Down
117 changes: 89 additions & 28 deletions birdwatcher/bird_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package birdwatcher
import (
"net"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -12,44 +13,104 @@ import (
func TestWriteBirdConfig(t *testing.T) {
t.Parallel()

// open tempfile
tmpFile, err := os.CreateTemp("", "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())
t.Run("empty config", func(t *testing.T) {
t.Parallel()

prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet("match_route")
// open tempfile
tmpFile, err := os.CreateTemp("", "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

// write bird config with empty prefix list
err = writeBirdConfig(tmpFile.Name(), prefixes)
require.NoError(t, err)
prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet("match_route")

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)
// write bird config with empty prefix list
err = writeBirdConfig(tmpFile.Name(), prefixes, false)
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config_empty")
require.NoError(t, err)
// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

assert.Equal(t, fixture, data)
fixture, err := os.ReadFile("testdata/bird/config_empty")
require.NoError(t, err)

for _, pref := range []string{"1.2.3.4/32", "2.3.4.5/26", "3.4.5.6/24", "4.5.6.7/21"} {
_, prf, _ := net.ParseCIDR(pref)
prefixes["match_route"].Add(*prf)
}
assert.Equal(t, string(fixture), string(data))
})

// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes)
require.NoError(t, err)
t.Run("one prefixset", func(t *testing.T) {
t.Parallel()

// read data from temp file and compare it to file fixture
data, err = os.ReadFile(tmpFile.Name())
require.NoError(t, err)
// open tempfile
tmpFile, err := os.CreateTemp("", "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

fixture, err = os.ReadFile("testdata/bird/config")
require.NoError(t, err)
prefixes := make(PrefixCollection)
prefixes["match_route"] = NewPrefixSet("match_route")

for _, pref := range []string{"1.2.3.4/32", "2.3.4.5/26", "3.4.5.6/24", "4.5.6.7/21"} {
_, prf, _ := net.ParseCIDR(pref)
prefixes["match_route"].Add(*prf)
}

// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes, false)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))
})

t.Run("one prefix, compat", func(t *testing.T) {
t.Parallel()

// open tempfile
tmpFile, err := os.CreateTemp("", "bird_test")
require.NoError(t, err)
defer os.Remove(tmpFile.Name())

prefixes := make(PrefixCollection)

prefixes["other_function"] = NewPrefixSet("other_function")
for _, pref := range []string{"5.6.7.8/32", "6.7.8.9/26", "7.8.9.10/24"} {
_, prf, _ := net.ParseCIDR(pref)
prefixes["other_function"].Add(*prf)
}

// write bird config to it
err = writeBirdConfig(tmpFile.Name(), prefixes, true)
require.NoError(t, err)

// read data from temp file and compare it to file fixture
data, err := os.ReadFile(tmpFile.Name())
require.NoError(t, err)

fixture, err := os.ReadFile("testdata/bird/config_compat")
require.NoError(t, err)

assert.Equal(t, string(fixture), string(data))
})
}

func TestPrefixPad(t *testing.T) {
t.Parallel()

prefixes := make([]net.IPNet, 4)

for i, pref := range []string{"1.2.3.0/24", "2.3.4.0/24", "3.4.5.0/24", "3.4.5.0/26"} {
_, prf, _ := net.ParseCIDR(pref)
prefixes[i] = *prf
}

assert.Equal(t, fixture, data)
padded := prefixPad(prefixes)
assert.Equal(t, "1.2.3.0/24,2.3.4.0/24,3.4.5.0/24,3.4.5.0/26", strings.Join(padded, ""))
}

func TestBirdCompareFiles(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions birdwatcher/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
type Config struct {
ConfigFile string
ReloadCommand string
CompatBird213 bool
Prometheus PrometheusConfig
Services map[string]*ServiceCheck
}
Expand Down
2 changes: 2 additions & 0 deletions birdwatcher/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ func TestConfig(t *testing.T) {

assert.Equal(t, "/etc/birdwatcher.conf", testConf.ConfigFile)
assert.Equal(t, "/sbin/birdc configure", testConf.ReloadCommand)
assert.True(t, testConf.CompatBird213)

assert.True(t, testConf.Prometheus.Enabled)
assert.Equal(t, 1234, testConf.Prometheus.Port)
assert.Equal(t, "/something", testConf.Prometheus.Path)
Expand Down
4 changes: 2 additions & 2 deletions birdwatcher/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewHealthCheck(c Config) HealthCheck {

// Start starts the process of health checking the services and handling
// Actions that come from them
func (h *HealthCheck) Start(services []*ServiceCheck, ready chan bool, status *chan string) {
func (h *HealthCheck) Start(services []*ServiceCheck, ready chan<- bool, status *chan string) {
// copy reference to services
h.services = services
// create channel for service check to push there events on
Expand Down Expand Up @@ -157,7 +157,7 @@ func (h *HealthCheck) applyConfig(config Config, prefixes PrefixCollection) erro
})

// update bird config
err := updateBirdConfig(config.ConfigFile, prefixes)
err := updateBirdConfig(config, prefixes)
if err != nil {
// if config did not change, we should still reload if we don't know the
// state of BIRD
Expand Down
56 changes: 10 additions & 46 deletions birdwatcher/prefixset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,10 @@ import (
// use embed for embedding the function template
_ "embed"
"net"
"text/template"

log "github.com/sirupsen/logrus"
)

//go:embed templates/function.tpl
var functionTemplate string

var tplFuncs = template.FuncMap{
"prefpad": prefixPad,
}

// PrefixCollection represents prefixsets per function name
type PrefixCollection map[string]*PrefixSet

Expand All @@ -31,6 +23,16 @@ func NewPrefixSet(functionName string) *PrefixSet {
return &PrefixSet{functionName: functionName}
}

// FunctionName returns the function name
func (p PrefixSet) FunctionName() string {
return p.functionName
}

// Prefixes returns the prefixes
func (p PrefixSet) Prefixes() []net.IPNet {
return p.prefixes
}

// Add adds a prefix to the PrefixSet if it wasn't already in it
func (p *PrefixSet) Add(prefix net.IPNet) {
pLog := log.WithFields(log.Fields{
Expand Down Expand Up @@ -72,41 +74,3 @@ func (p *PrefixSet) Remove(prefix net.IPNet) {

pLog.Warn("prefix not found in prefix set, skipping")
}

// Marshal returns the BIRD function for this prefixset
func (p PrefixSet) Marshal() string {
// init template
tmpl := template.Must(template.New("func").Funcs(tplFuncs).Parse(functionTemplate))

// init template body
tplBody := struct {
FunctionName string
Prefixes []net.IPNet
}{
FunctionName: p.functionName,
Prefixes: p.prefixes,
}

// execute template and return output
var buf bytes.Buffer
if err := tmpl.Execute(&buf, tplBody); err != nil {
log.WithError(err).Error("could not parse template body")
}

return buf.String()
}

// prefixPad is a helper function for the template
// basically returns CIDR notations per IPNet, each suffixed with a , except for
// the last entry
func prefixPad(x []net.IPNet) []string {
pp := make([]string, len(x))
for i, p := range x {
pp[i] = p.String()
if i < len(x)-1 {
pp[i] += ","
}
}

return pp
}
Loading

0 comments on commit d0f2bdc

Please sign in to comment.