You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
package configapi
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/darkspot-org/bathyscaphe/internal/cache"
|
|
"github.com/darkspot-org/bathyscaphe/internal/event"
|
|
"github.com/darkspot-org/bathyscaphe/internal/process"
|
|
"github.com/gorilla/mux"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/urfave/cli/v2"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// State represent the application state
|
|
type State struct {
|
|
configCache cache.Cache
|
|
pub event.Publisher
|
|
}
|
|
|
|
// Name return the process name
|
|
func (state *State) Name() string {
|
|
return "configapi"
|
|
}
|
|
|
|
// Description return the process description
|
|
func (state *State) Description() string {
|
|
return `
|
|
The ConfigAPI component. It serves as a centralized K/V database
|
|
with notification support.
|
|
This component expose a REST API to allow other process to retrieve
|
|
configuration as startup time, and to allow value update at runtime.
|
|
Each time a configuration is update trough the API, an event will
|
|
be dispatched so that running processes can update their local values.
|
|
|
|
This component produces the 'config' event.`
|
|
}
|
|
|
|
// Features return the process features
|
|
func (state *State) Features() []process.Feature {
|
|
return []process.Feature{process.EventFeature, process.CacheFeature}
|
|
}
|
|
|
|
// CustomFlags return process custom flags
|
|
func (state *State) CustomFlags() []cli.Flag {
|
|
return []cli.Flag{
|
|
&cli.StringSliceFlag{
|
|
Name: "default-value",
|
|
Usage: "Set default value of key. (format key=value)",
|
|
},
|
|
}
|
|
}
|
|
|
|
// Initialize the process
|
|
func (state *State) Initialize(provider process.Provider) error {
|
|
configCache, err := provider.Cache("configuration")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
state.configCache = configCache
|
|
|
|
pub, err := provider.Publisher()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
state.pub = pub
|
|
|
|
defaultValues := map[string]string{}
|
|
for _, value := range provider.GetStrValues("default-value") {
|
|
parts := strings.Split(value, "=")
|
|
|
|
if len(parts) == 2 {
|
|
defaultValues[parts[0]] = parts[1]
|
|
}
|
|
}
|
|
if len(defaultValues) > 0 {
|
|
if err := setDefaultValues(configCache, defaultValues); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil // TODO
|
|
}
|
|
|
|
// Subscribers return the process subscribers
|
|
func (state *State) Subscribers() []process.SubscriberDef {
|
|
return []process.SubscriberDef{}
|
|
}
|
|
|
|
// HTTPHandler returns the HTTP API the process expose
|
|
func (state *State) HTTPHandler() http.Handler {
|
|
r := mux.NewRouter()
|
|
r.HandleFunc("/config/{key}", state.getConfiguration).Methods(http.MethodGet)
|
|
r.HandleFunc("/config/{key}", state.setConfiguration).Methods(http.MethodPut)
|
|
|
|
return r
|
|
}
|
|
|
|
func (state *State) getConfiguration(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
key := vars["key"]
|
|
|
|
log.Debug().Str("key", key).Msg("Getting key")
|
|
|
|
b, err := state.configCache.GetBytes(key)
|
|
if err != nil {
|
|
log.Err(err).Msg("error while retrieving configuration")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write(b)
|
|
}
|
|
|
|
func (state *State) setConfiguration(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
key := vars["key"]
|
|
|
|
b, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
log.Err(err).Msg("error while reading body")
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
return
|
|
}
|
|
|
|
log.Debug().Str("key", key).Bytes("value", b).Msg("Setting key")
|
|
|
|
if err := state.configCache.SetBytes(key, b, cache.NoTTL); err != nil {
|
|
log.Err(err).Msg("error while setting configuration")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// publish event to notify config changed
|
|
if err := state.pub.PublishJSON(event.ConfigExchange, event.RawMessage{
|
|
Body: b,
|
|
Headers: map[string]interface{}{"Config-Key": key},
|
|
}); err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write(b)
|
|
}
|
|
|
|
func setDefaultValues(configCache cache.Cache, values map[string]string) error {
|
|
for key, value := range values {
|
|
b, err := configCache.GetBytes(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if b == nil {
|
|
if err := configCache.SetBytes(key, []byte(value), cache.NoTTL); err != nil {
|
|
return fmt.Errorf("error while setting default value of %s: %s", key, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|