Skip to content

Commit 1baaa6e

Browse files
committed
Add config service
1 parent c25a1eb commit 1baaa6e

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

home-automation/03-config/config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
base:
2+
apiGateway: http://service.api-gateway
3+
redis:
4+
host: redis
5+
port: 6379
6+
7+
service.registry.device:
8+
database: /data/devices.db
9+
apiGateway: somethingNew
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package controller
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/gorilla/mux"
9+
"github.com/jakewright/tutorials/home-automation/03-config/domain"
10+
)
11+
12+
// Controller exports the handlers for the endpoints
13+
type Controller struct {
14+
Config *domain.Config
15+
}
16+
17+
// ReadConfig writes the config for the given service to the ResponseWriter
18+
func (c *Controller) ReadConfig(w http.ResponseWriter, r *http.Request) {
19+
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
20+
21+
vars := mux.Vars(r)
22+
serviceName, ok := vars["serviceName"]
23+
if !ok {
24+
w.WriteHeader(http.StatusBadRequest)
25+
fmt.Fprintf(w, "error")
26+
}
27+
28+
config, err := c.Config.Get(serviceName)
29+
if err != nil {
30+
w.WriteHeader(http.StatusInternalServerError)
31+
fmt.Fprintf(w, "error")
32+
}
33+
34+
rsp, err := json.Marshal(&config)
35+
if err != nil {
36+
w.WriteHeader(http.StatusInternalServerError)
37+
fmt.Fprintf(w, "error")
38+
}
39+
40+
w.WriteHeader(http.StatusOK)
41+
fmt.Fprintf(w, string(rsp))
42+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package domain
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
"gopkg.in/yaml.v2"
8+
)
9+
10+
// Config is an abstraction around the map that holds the config values
11+
type Config struct {
12+
config map[string]interface{}
13+
lock sync.RWMutex
14+
}
15+
16+
// SetFromBytes sets the internal config based on a byte array of YAML
17+
func (c *Config) SetFromBytes(data []byte) error {
18+
var rawConfig interface{}
19+
if err := yaml.Unmarshal(data, &rawConfig); err != nil {
20+
return err
21+
}
22+
23+
untypedConfig, ok := rawConfig.(map[interface{}]interface{})
24+
if !ok {
25+
return fmt.Errorf("config is not a map")
26+
}
27+
28+
config, err := convertKeysToStrings(untypedConfig)
29+
if err != nil {
30+
return err
31+
}
32+
33+
c.lock.Lock()
34+
defer c.lock.Unlock()
35+
36+
c.config = config
37+
return nil
38+
}
39+
40+
// Get returns the config for a particular service
41+
func (c *Config) Get(serviceName string) (map[string]interface{}, error) {
42+
c.lock.RLock()
43+
defer c.lock.RUnlock()
44+
45+
a, ok := c.config["base"].(map[string]interface{})
46+
if !ok {
47+
return nil, fmt.Errorf("base config is not a map")
48+
}
49+
50+
// If no config is defined for the service
51+
if _, ok = c.config[serviceName]; !ok {
52+
// Return the base config
53+
return a, nil
54+
}
55+
56+
b, ok := c.config[serviceName].(map[string]interface{})
57+
if !ok {
58+
return nil, fmt.Errorf("service %q config is not a map", serviceName)
59+
}
60+
61+
// Merge the maps with the service config taking precedence
62+
config := make(map[string]interface{})
63+
for k, v := range a {
64+
config[k] = v
65+
}
66+
for k, v := range b {
67+
config[k] = v
68+
}
69+
70+
return config, nil
71+
}
72+
73+
func convertKeysToStrings(m map[interface{}]interface{}) (map[string]interface{}, error) {
74+
n := make(map[string]interface{})
75+
76+
for k, v := range m {
77+
str, ok := k.(string)
78+
if !ok {
79+
return nil, fmt.Errorf("config key is not a string")
80+
}
81+
82+
if vMap, ok := v.(map[interface{}]interface{}); ok {
83+
var err error
84+
v, err = convertKeysToStrings(vMap)
85+
if err != nil {
86+
return nil, err
87+
}
88+
}
89+
90+
n[str] = v
91+
}
92+
93+
return n, nil
94+
}

home-automation/03-config/main.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"time"
6+
7+
"github.com/jakewright/muxinator"
8+
9+
"github.com/jakewright/tutorials/home-automation/03-config/controller"
10+
"github.com/jakewright/tutorials/home-automation/03-config/domain"
11+
"github.com/jakewright/tutorials/home-automation/03-config/service"
12+
)
13+
14+
func main() {
15+
config := domain.Config{}
16+
17+
configService := service.ConfigService{
18+
Config: &config,
19+
Location: "config.yaml",
20+
}
21+
22+
go configService.Watch(time.Second * 30)
23+
24+
c := controller.Controller{
25+
Config: &config,
26+
}
27+
28+
router := muxinator.NewRouter()
29+
router.Get("/read/{serviceName}", c.ReadConfig)
30+
log.Fatal(router.ListenAndServe(":8080"))
31+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package service
2+
3+
import (
4+
"io/ioutil"
5+
"log"
6+
"time"
7+
8+
"github.com/jakewright/tutorials/home-automation/03-config/domain"
9+
)
10+
11+
type ConfigService struct {
12+
Config *domain.Config
13+
Location string
14+
}
15+
16+
// Watch reloads the config every d duration
17+
func (s *ConfigService) Watch(d time.Duration) {
18+
for {
19+
err := s.Reload()
20+
if err != nil {
21+
log.Print(err)
22+
}
23+
24+
time.Sleep(d)
25+
}
26+
}
27+
28+
// Reload reads the config and applies changes
29+
func (s *ConfigService) Reload() error {
30+
data, err := ioutil.ReadFile(s.Location)
31+
if err != nil {
32+
return err
33+
}
34+
35+
err = s.Config.SetFromBytes(data)
36+
if err != nil {
37+
return err
38+
}
39+
40+
return nil
41+
}

0 commit comments

Comments
 (0)