├── README.md ├── config.go └── server.go /README.md: -------------------------------------------------------------------------------- 1 | Scut 2 | ==== 3 | 4 | ### Manage your configs Like a Boss! 5 | 6 | Scut is a package that provide config and server for your config, and with 7 | scut-server you can get or set values of your config. 8 | 9 | Very useful for daemons which use some configurations. 10 | 11 | ## Installation 12 | 13 | `go get github.com/kovetskiy/scut` 14 | 15 | ## Usage 16 | 17 | ```go 18 | // First, we need to create Config structure.. 19 | config, err := scut.NewConfig("config.toml") 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | // See https://github.com/zazab/zhash 25 | someHost, err := config.GetString("mysql", "host") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | somePort, err := config.GetInt("mysql", "port") 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | fmt.Printf("%s:%d\n", someHost, somePort) 36 | 37 | // Okay, then we will start our daemon and we want to change stored data in 38 | // initialized config structure, 39 | // 40 | // We are creating ConfigServer, and giving to it our config. 41 | server, err := scut.NewConfigServer(config) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | // Good, we have a server, then we should start it, for example on 8080 port. 47 | go server.Listen(":8080") 48 | ``` 49 | 50 | For example we have `config.toml`: 51 | 52 | ```toml 53 | simpleKey="simpleValue" 54 | 55 | [array_values] 56 | keyA="valueA" 57 | keyB="valueB" 58 | ``` 59 | 60 | App with scut.ConfigServer is started on 8080 port. 61 | 62 | ### Operations 63 | 64 | * Get all data with GET method: 65 | ``` 66 | $ curl http://localhost:8080/ 67 | { 68 | "array_values": { 69 | "keyA": "valueA", 70 | "keyB": "valueB" 71 | }, 72 | "simpleKey": "simpleValue" 73 | } 74 | ``` 75 | 76 | * Get item: 77 | ``` 78 | $ curl http://localhost:8080/simpleKey 79 | "simpleValue" 80 | ``` 81 | 82 | * Get nested item: 83 | ``` 84 | $ curl http://localhost:8080/array_values/keyA 85 | "valueA" 86 | ``` 87 | 88 | * Get all nested items in `array_values`: 89 | ``` 90 | $ curl http://localhost:8080/array_values/ 91 | { 92 | "keyA": "valueA", 93 | "keyB": "valueB" 94 | } 95 | ``` 96 | 97 | * Change stored data in `array_values.keyA`: 98 | ``` 99 | $ curl -X PATCH --data '"changedValueA"' \ 100 | http://localhost:8080/array_values/keyA 101 | ``` 102 | 103 | Gotcha! 104 | 105 | ``` 106 | $ curl http://localhost:8080/array_values/ 107 | { 108 | "keyA": "changedValueA", 109 | "keyB": "valueB" 110 | } 111 | ``` 112 | 113 | ``` 114 | $ curl http://localhost:8080/ 115 | { 116 | "array_values": { 117 | "keyA": "changedValueA", 118 | "keyB": "valueB" 119 | }, 120 | "simpleKey": "simpleValue" 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package scut 2 | 3 | import ( 4 | "github.com/BurntSushi/toml" 5 | "github.com/zazab/zhash" 6 | ) 7 | 8 | type Config struct { 9 | zhash.Hash 10 | filepath string 11 | } 12 | 13 | func NewConfig(filepath string) (*Config, error) { 14 | tomlMap := map[string]interface{}{} 15 | _, err := toml.DecodeFile(filepath, &tomlMap) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | hash := zhash.HashFromMap(tomlMap) 21 | return &Config{Hash: hash, filepath: filepath}, nil 22 | } 23 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package scut 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type ConfigServer struct { 11 | config *Config 12 | } 13 | 14 | func NewConfigServer(config *Config) (*ConfigServer, error) { 15 | if config == nil { 16 | return nil, fmt.Errorf("config must be pointer") 17 | } 18 | 19 | server := ConfigServer{config: config} 20 | 21 | return &server, nil 22 | } 23 | 24 | func (server *ConfigServer) Listen(address string) error { 25 | return http.ListenAndServe(address, server) 26 | } 27 | 28 | func (server ConfigServer) ServeHTTP( 29 | writer http.ResponseWriter, request *http.Request, 30 | ) { 31 | var ( 32 | path = strings.Split(strings.Trim(request.URL.Path, "/"), "/") 33 | method = strings.ToUpper(request.Method) 34 | 35 | body interface{} 36 | ) 37 | 38 | if method != "GET" { 39 | err := json.NewDecoder(request.Body).Decode(&body) 40 | if err != nil { 41 | writer.WriteHeader(400) 42 | return 43 | } 44 | defer request.Body.Close() 45 | } 46 | 47 | switch request.Method { 48 | case "GET": 49 | server.handleGET(writer, path...) 50 | case "PATCH": 51 | server.handlePATCH(writer, body, path...) 52 | case "PUT": 53 | server.handlePUT(writer, body) 54 | default: 55 | writer.WriteHeader(405) 56 | } 57 | } 58 | 59 | func (server *ConfigServer) handleGET( 60 | writer http.ResponseWriter, path ...string, 61 | ) { 62 | var data interface{} 63 | 64 | if len(path) == 1 && path[0] == "" { 65 | data = server.config.GetRoot() 66 | } else { 67 | data = server.config.Get(path...) 68 | if data == nil { 69 | writer.WriteHeader(404) 70 | return 71 | } 72 | } 73 | 74 | jsonedData, _ := json.MarshalIndent(data, "", " ") 75 | writer.Write(jsonedData) 76 | } 77 | 78 | func (server *ConfigServer) handlePATCH( 79 | writer http.ResponseWriter, value interface{}, path ...string, 80 | ) { 81 | server.config.Set(value, path...) 82 | } 83 | 84 | func (server *ConfigServer) handlePUT( 85 | writer http.ResponseWriter, value interface{}, 86 | ) { 87 | switch root := value.(type) { 88 | case map[string]interface{}: 89 | server.config.SetRoot(root) 90 | default: 91 | writer.WriteHeader(400) 92 | } 93 | } 94 | --------------------------------------------------------------------------------