1
0
mirror of https://github.com/fumiama/terasu-cloudflared.git synced 2026-06-09 12:40:35 +08:00

AUTH-2587 add config watcher and reload logic for access client forwarder

This commit is contained in:
Dalton
2020-04-13 12:22:00 -05:00
parent 976eb24883
commit 41c358147c
32 changed files with 2929 additions and 8 deletions

View File

@@ -0,0 +1,101 @@
package config
import (
"errors"
"os"
"github.com/cloudflare/cloudflared/watcher"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
// Notifier sends out config updates
type Notifier interface {
ConfigDidUpdate(Root)
}
// Manager is the base functions of the config manager
type Manager interface {
Start(Notifier) error
Shutdown()
}
// FileManager watches the yaml config for changes
// sends updates to the service to reconfigure to match the updated config
type FileManager struct {
watcher watcher.Notifier
notifier Notifier
configPath string
logger *logrus.Logger
}
// NewFileManager creates a config manager
func NewFileManager(watcher watcher.Notifier, configPath string, logger *logrus.Logger) (Manager, error) {
m := &FileManager{
watcher: watcher,
configPath: configPath,
logger: logger,
}
err := watcher.Add(configPath)
return m, err
}
// Start starts the runloop to watch for config changes
func (m *FileManager) Start(notifier Notifier) error {
m.notifier = notifier
// update the notifier with a fresh config on start
config, err := m.GetConfig()
if err != nil {
return err
}
notifier.ConfigDidUpdate(config)
m.watcher.Start(m)
return nil
}
// GetConfig reads the yaml file from the disk
func (m *FileManager) GetConfig() (Root, error) {
if m.configPath == "" {
return Root{}, errors.New("unable to find config file")
}
file, err := os.Open(m.configPath)
if err != nil {
return Root{}, err
}
defer file.Close()
var config Root
if err := yaml.NewDecoder(file).Decode(&config); err != nil {
return Root{}, err
}
return config, nil
}
// Shutdown stops the watcher
func (m *FileManager) Shutdown() {
m.watcher.Shutdown()
}
// File change notifications from the watcher
// WatcherItemDidChange triggers when the yaml config is updated
// sends the updated config to the service to reload its state
func (m *FileManager) WatcherItemDidChange(filepath string) {
config, err := m.GetConfig()
if err != nil {
m.logger.WithError(err).Error("Failed to read new config")
return
}
m.logger.Info("Config file has been updated")
m.notifier.ConfigDidUpdate(config)
}
// WatcherDidError notifies of errors with the file watcher
func (m *FileManager) WatcherDidError(err error) {
m.logger.WithError(err).Error("Config watcher encountered an error")
}

View File

@@ -0,0 +1,80 @@
package config
import (
"bufio"
"os"
"testing"
"time"
"github.com/cloudflare/cloudflared/log"
"github.com/cloudflare/cloudflared/watcher"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
)
type mockNotifier struct {
configs []Root
}
func (n *mockNotifier) ConfigDidUpdate(c Root) {
n.configs = append(n.configs, c)
}
func writeConfig(t *testing.T, f *os.File, c *Root) {
f.Sync()
b, err := yaml.Marshal(c)
assert.NoError(t, err)
w := bufio.NewWriter(f)
_, err = w.Write(b)
assert.NoError(t, err)
err = w.Flush()
assert.NoError(t, err)
}
func TestConfigChanged(t *testing.T) {
filePath := "config.yaml"
f, err := os.Create(filePath)
assert.NoError(t, err)
defer func() {
f.Close()
os.Remove(filePath)
}()
c := &Root{
OrgKey: "abcd",
ConfigType: "mytype",
CheckinInterval: 1,
Forwarders: []Forwarder{
{
URL: "test.daltoniam.com",
Listener: "127.0.0.1:8080",
},
},
}
writeConfig(t, f, c)
w, err := watcher.NewFile()
assert.NoError(t, err)
logger := log.CreateLogger()
service, err := NewFileManager(w, filePath, logger)
assert.NoError(t, err)
n := &mockNotifier{}
go service.Start(n)
c.Forwarders = append(c.Forwarders, Forwarder{URL: "add.daltoniam.com", Listener: "127.0.0.1:8081"})
writeConfig(t, f, c)
// give it time to trigger
time.Sleep(10 * time.Millisecond)
service.Shutdown()
assert.Len(t, n.configs, 2, "did not get 2 config updates as expected")
assert.Len(t, n.configs[0].Forwarders, 1, "not the amount of forwarders expected")
assert.Len(t, n.configs[1].Forwarders, 2, "not the amount of forwarders expected")
assert.Equal(t, n.configs[0].Forwarders[0].Hash(), c.Forwarders[0].Hash(), "forwarder hashes don't match")
assert.Equal(t, n.configs[1].Forwarders[0].Hash(), c.Forwarders[0].Hash(), "forwarder hashes don't match")
assert.Equal(t, n.configs[1].Forwarders[1].Hash(), c.Forwarders[1].Hash(), "forwarder hashes don't match")
}

View File

@@ -0,0 +1,37 @@
package config
import (
"crypto/md5"
"fmt"
"io"
)
// Forwarder represents a client side listener to forward traffic to the edge
type Forwarder struct {
URL string `json:"url"`
Listener string `json:"listener"`
}
// Tunnel represents a tunnel that should be started
type Tunnel struct {
URL string `json:"url"`
Origin string `json:"origin"`
ProtocolType string `json:"type"`
}
// Root is the base options to configure the service
type Root struct {
OrgKey string `json:"org_key"`
ConfigType string `json:"type"`
CheckinInterval int `json:"checkin_interval"`
Forwarders []Forwarder `json:"forwarders,omitempty"`
Tunnels []Tunnel `json:"tunnels,omitempty"`
}
// Hash returns the computed values to see if the forwarder values change
func (f *Forwarder) Hash() string {
h := md5.New()
io.WriteString(h, f.URL)
io.WriteString(h, f.Listener)
return fmt.Sprintf("%x", h.Sum(nil))
}