-
Notifications
You must be signed in to change notification settings - Fork 1
/
config.go
148 lines (123 loc) · 3.21 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
Copyright © 2022 Francisco de Borja Aranda Castillejo me@fbac.dev
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"github.com/fsnotify/fsnotify"
)
func NewConfigStore(path string) *ConfigStore {
return &ConfigStore{path: path}
}
// ConfigStore manages reading and watching for changes to a config file
type ConfigStore struct {
path string
watcher *fsnotify.Watcher
updateCh chan Config
}
// StartWatcher begins monitoring the config file for changes.
func (c *ConfigStore) StartWatcher() (<-chan Config, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
if err := watcher.Add(c.path); err != nil {
return nil, err
}
c.watcher = watcher
ch := make(chan Config)
c.updateCh = ch
go func() {
defer close(c.updateCh)
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("config file modified")
cfg, err := readConfigFile(event.Name)
if err != nil {
log.Printf("error loading config file %s: %s", event.Name, err)
} else {
ch <- cfg
}
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error watching config file:", err)
}
}
}()
return ch, nil
}
// Close stops monitoring for changes
func (c *ConfigStore) Close() error {
if c.watcher != nil {
defer func() {
c.watcher = nil
}()
return c.watcher.Close()
}
return nil
}
// Read loads and validates the config file
func (c ConfigStore) Read() (Config, error) {
return readConfigFile(c.path)
}
func readConfigFile(path string) (Config, error) {
var cfg Config
data, err := ioutil.ReadFile(path)
if err != nil {
return cfg, fmt.Errorf("error reading config file: %w", err)
}
if err := json.Unmarshal(data, &cfg); err != nil {
return cfg, fmt.Errorf("error loading config: %w", err)
}
if err := validateConfig(cfg); err != nil {
return cfg, err
}
return cfg, nil
}
func validateConfig(cfg Config) error {
ports := map[int]struct{}{}
for _, app := range cfg.Apps {
for _, port := range app.Ports {
if _, ok := ports[port]; ok {
return errors.New("invalid configuration - duplicate ports")
}
ports[port] = struct{}{}
}
}
return nil
}
type Config struct {
// Apps is the list of configured apps
Apps []App
}
// App holds configuration for a single "App"
type App struct {
// Name is a friendly name to assist in debugging
Name string
// Ports is the list of ports this app will respond to
Ports []int
// Targets is the list of backend targets this app will proxy to
Targets []string
}