340 lines
8.2 KiB
Go
Executable File
340 lines
8.2 KiB
Go
Executable File
/* ©INFINI, All Rights Reserved.
|
|
* mail: contact#infini.ltd */
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
log "github.com/cihub/seelog"
|
|
"infini.sh/console/core"
|
|
"infini.sh/console/plugin/managed/common"
|
|
"infini.sh/framework/core/api"
|
|
httprouter "infini.sh/framework/core/api/router"
|
|
"infini.sh/framework/core/env"
|
|
"infini.sh/framework/core/errors"
|
|
"infini.sh/framework/core/global"
|
|
"infini.sh/framework/core/util"
|
|
"infini.sh/framework/core/util/file"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func init() {
|
|
api.HandleAPIMethod(api.GET, "/config/", h.listConfigAction)
|
|
api.HandleAPIMethod(api.PUT, "/config/", h.saveConfigAction)
|
|
api.HandleAPIMethod(api.DELETE, "/config/", h.deleteConfigAction)
|
|
api.HandleAPIMethod(api.POST, "/config/_reload", h.reloadConfigAction)
|
|
api.HandleAPIMethod(api.GET, "/config/runtime", h.getConfigAction)
|
|
api.HandleAPIMethod(api.GET, "/environments", h.getEnvAction)
|
|
|
|
}
|
|
|
|
var h = DefaultHandler{}
|
|
|
|
type DefaultHandler struct {
|
|
core.Handler
|
|
}
|
|
|
|
func (handler DefaultHandler) getEnvAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
handler.WriteJSONHeader(w)
|
|
handler.WriteJSON(w, os.Environ(), 200)
|
|
}
|
|
|
|
func (handler DefaultHandler) getConfigAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
json := env.GetConfigAsJSON()
|
|
handler.WriteJSONHeader(w)
|
|
handler.Write(w, []byte(json))
|
|
}
|
|
|
|
func validateFile(cfgDir, name string) error {
|
|
|
|
cfgDir, _ = filepath.Abs(cfgDir)
|
|
name, _ = filepath.Abs(name)
|
|
|
|
//filter out the hidden files and go files
|
|
if strings.HasPrefix(filepath.Base(name), ".") || strings.HasSuffix(filepath.Base(name), ".go") {
|
|
return errors.Errorf("invalid config filename")
|
|
}
|
|
|
|
//filetype checking
|
|
ext := filepath.Ext(name)
|
|
if len(global.Env().SystemConfig.Configs.ValidConfigsExtensions) > 0 && !util.ContainsAnyInArray(ext, global.Env().SystemConfig.Configs.ValidConfigsExtensions) {
|
|
return errors.Errorf("invalid config file: %s, only support: %v", name, global.Env().SystemConfig.Configs.ValidConfigsExtensions)
|
|
}
|
|
|
|
//permission checking
|
|
if !util.IsFileWithinFolder(name, cfgDir) {
|
|
return errors.Errorf("invalid config file: %s, outside of path: %v", name, cfgDir)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DeleteConfig(name string) error {
|
|
|
|
cfgDir, err := filepath.Abs(global.Env().GetConfigDir())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fileToDelete := path.Join(cfgDir, name)
|
|
|
|
log.Info("delete config file: ", fileToDelete)
|
|
|
|
//file checking
|
|
if err := validateFile(cfgDir, fileToDelete); err != nil {
|
|
return err
|
|
}
|
|
|
|
if util.FileExists(fileToDelete) {
|
|
if global.Env().SystemConfig.Configs.SoftDelete {
|
|
err := util.Rename(fileToDelete, fmt.Sprintf("%v.%v.bak", fileToDelete, time.Now().UnixMilli()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
err := util.FileDelete(fileToDelete)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
return errors.Errorf("file not exists: %s", fileToDelete)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func SaveConfig(name string, cfg common.ConfigFile) error {
|
|
|
|
//update version
|
|
if cfg.Managed {
|
|
cfg.Content = updateConfigVersion(cfg.Content, cfg.Version)
|
|
cfg.Content = updateConfigManaged(cfg.Content, true)
|
|
}
|
|
|
|
return SaveConfigStr(name, cfg.Content)
|
|
}
|
|
|
|
func SaveConfigStr(name, content string) error {
|
|
|
|
cfgDir, err := filepath.Abs(global.Env().GetConfigDir())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fileToSave := path.Join(cfgDir, name)
|
|
|
|
log.Info("write config file: ", fileToSave)
|
|
log.Trace("file content: ", content)
|
|
|
|
if err := validateFile(cfgDir, fileToSave); err != nil {
|
|
return err
|
|
}
|
|
|
|
if util.FileExists(fileToSave) {
|
|
if global.Env().SystemConfig.Configs.SoftDelete {
|
|
err := util.Rename(fileToSave, fmt.Sprintf("%v.%v.bak", fileToSave, time.Now().UnixMilli()))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err = util.FilePutContent(fileToSave, content)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (handler DefaultHandler) deleteConfigAction(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
reqBody := common.ConfigDeleteRequest{}
|
|
err := handler.DecodeJSON(req, &reqBody)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for _, name := range reqBody.Configs {
|
|
err := DeleteConfig(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
handler.WriteAckOKJSON(w)
|
|
}
|
|
|
|
var versionRegexp = regexp.MustCompile(`#MANAGED_CONFIG_VERSION:\s*(?P<version>\d+)`)
|
|
var managedRegexp = regexp.MustCompile(`#MANAGED:\s*(?P<managed>\w+)`)
|
|
|
|
func parseConfigVersion(input string) int64 {
|
|
matches := versionRegexp.FindStringSubmatch(util.TrimSpaces(input))
|
|
if len(matches) > 0 {
|
|
ver := versionRegexp.SubexpIndex("version")
|
|
if ver >= 0 {
|
|
str := (matches[ver])
|
|
v, err := util.ToInt64(util.TrimSpaces(str))
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
return v
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func parseConfigManaged(input string, defaultManaged bool) bool {
|
|
matches := managedRegexp.FindStringSubmatch(util.TrimSpaces(input))
|
|
if len(matches) > 0 {
|
|
v := managedRegexp.SubexpIndex("managed")
|
|
if v >= 0 {
|
|
str := util.TrimSpaces(strings.ToLower(matches[v]))
|
|
if str == "true" {
|
|
return true
|
|
} else if str == "false" {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return defaultManaged
|
|
}
|
|
|
|
func updateConfigManaged(input string, managed bool) string {
|
|
if managedRegexp.MatchString(input) {
|
|
return managedRegexp.ReplaceAllString(input, fmt.Sprintf("#MANAGED: %v", managed))
|
|
} else {
|
|
return fmt.Sprintf("%v\n#MANAGED: %v", input, managed)
|
|
}
|
|
}
|
|
|
|
func updateConfigVersion(input string, newVersion int64) string {
|
|
if versionRegexp.MatchString(input) {
|
|
return versionRegexp.ReplaceAllString(input, fmt.Sprintf("#MANAGED_CONFIG_VERSION: %d", newVersion))
|
|
} else {
|
|
return fmt.Sprintf("%v\n#MANAGED_CONFIG_VERSION: %d", input, newVersion)
|
|
}
|
|
}
|
|
|
|
func (handler DefaultHandler) listConfigAction(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
configs := GetConfigs(true, false)
|
|
handler.WriteJSON(w, configs, 200)
|
|
}
|
|
|
|
func GetConfigs(returnContent, managedOnly bool) common.ConfigList {
|
|
|
|
cfgDir, err := filepath.Abs(global.Env().GetConfigDir())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
configs := common.ConfigList{}
|
|
configs.Configs = make(map[string]common.ConfigFile)
|
|
|
|
mainConfig, _ := filepath.Abs(global.Env().GetConfigFile())
|
|
|
|
info, err := file.Stat(mainConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
c, err := util.FileGetContent(mainConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
configs.Main = common.ConfigFile{
|
|
Name: util.TrimLeftStr(filepath.Base(mainConfig), cfgDir),
|
|
Location: mainConfig,
|
|
Readonly: true,
|
|
Managed: false,
|
|
Updated: info.ModTime().Unix(),
|
|
Size: info.Size(),
|
|
}
|
|
|
|
if returnContent {
|
|
configs.Main.Content = string(c)
|
|
}
|
|
|
|
//get files within folder
|
|
filepath.Walk(cfgDir, func(file string, f os.FileInfo, err error) error {
|
|
|
|
cfg, err := GetConfigFromFile(cfgDir, file)
|
|
if cfg != nil {
|
|
|
|
if !cfg.Managed && managedOnly {
|
|
return nil
|
|
}
|
|
|
|
if !returnContent {
|
|
cfg.Content = ""
|
|
}
|
|
|
|
configs.Configs[cfg.Name] = *cfg
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return configs
|
|
}
|
|
|
|
func GetConfigFromFile(cfgDir, filename string) (*common.ConfigFile, error) {
|
|
|
|
//file checking
|
|
if err := validateFile(cfgDir, filename); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c, err := util.FileGetContent(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := file.Stat(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
content := string(c)
|
|
cfg := common.ConfigFile{
|
|
Name: util.TrimLeftStr(filepath.Base(filename), cfgDir),
|
|
Location: filename,
|
|
Version: parseConfigVersion(content),
|
|
Updated: f.ModTime().Unix(),
|
|
Size: f.Size(),
|
|
}
|
|
|
|
cfg.Content = content
|
|
cfg.Managed = parseConfigManaged(content, cfg.Version > 0 || global.Env().SystemConfig.Configs.ConfigFileManagedByDefault)
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func (handler DefaultHandler) saveConfigAction(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
reqBody := common.ConfigUpdateRequest{}
|
|
err := handler.DecodeJSON(req, &reqBody)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
for name, content := range reqBody.Configs {
|
|
err := SaveConfigStr(name, content)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
handler.WriteAckOKJSON(w)
|
|
}
|
|
|
|
func (handler DefaultHandler) reloadConfigAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
|
log.Infof("refresh config")
|
|
err := global.Env().RefreshConfig()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
handler.WriteAckOKJSON(w)
|
|
}
|