Files
vxcommon/controller/loader.go
T
2021-03-22 12:45:34 +03:00

338 lines
9.1 KiB
Go

package controller
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/vxcontrol/vxcommon/db"
"github.com/vxcontrol/vxcommon/loader"
"github.com/vxcontrol/vxcommon/storage"
)
// tConfigLoaderType is type for loading config
type tConfigLoaderType int32
// Enum config loading types
const (
eDBConfigLoader tConfigLoaderType = 0
eS3ConfigLoader tConfigLoaderType = 1
eFSConfigLoader tConfigLoaderType = 2
)
// List of SQL queries string
const (
sLoadModulesSQL string = "SELECT `id`, IF(`agent_id` = 0, '', (SELECT `hash` FROM `agents` WHERE `id` = `agent_id`)) AS `agent_id`, `info`, `last_update` FROM `modules` WHERE `status` = 'joined'"
sGetModuleFieldSQL string = "SELECT `%s` FROM `modules` WHERE `id` = ? LIMIT 1"
sSetModuleFieldSQL string = "UPDATE `modules` SET `%s` = ? WHERE `id` = ?"
)
// tFilesLoaderType is type for loading module
type tFilesLoaderType int32
// Enum files loading types
const (
eS3FilesLoader tFilesLoaderType = 0
eFSFilesLoader tFilesLoaderType = 1
)
// IConfigLoader is internal interface for loading config from external storage
type IConfigLoader interface {
load() ([]*loader.ModuleConfig, error)
}
// IFilesLoader is internal interface for loading files from external storage
type IFilesLoader interface {
load(mcl []*loader.ModuleConfig) ([]*loader.ModuleFiles, error)
}
// configLoaderDB is container for config which loaded from DB
type configLoaderDB struct {
}
func (cl *configLoaderDB) getCb(id, col string) getCallback {
sql := fmt.Sprintf(sGetModuleFieldSQL, col)
return func() string {
db, err := db.New()
if err != nil {
return ""
}
rows, err := db.Query(sql, id)
if err != nil {
return ""
}
if len(rows) != 1 {
return ""
}
if data, ok := rows[0][col]; ok {
return data
}
return ""
}
}
func (cl *configLoaderDB) setCb(id, col string) setCallback {
sql := fmt.Sprintf(sSetModuleFieldSQL, col)
return func(val string) bool {
db, err := db.New()
if err != nil {
return false
}
_, err = db.Exec(sql, val, id)
if err != nil {
return false
}
return true
}
}
func joinPath(args ...string) string {
tpath := filepath.Join(args...)
return strings.Replace(tpath, "\\", "/", -1)
}
// load is function what retrieve modules config list from DB
func (cl *configLoaderDB) load() ([]*loader.ModuleConfig, error) {
db, err := db.New()
if err != nil {
return nil, err
}
rows, err := db.Query(sLoadModulesSQL)
if err != nil {
return nil, err
}
var ml []*loader.ModuleConfig
for _, m := range rows {
var mc loader.ModuleConfig
if info, ok := m["info"]; ok {
if err = json.Unmarshal([]byte(info), &mc); err != nil {
return nil, errors.New("error parsing module config: " + err.Error())
}
if agentID, ok := m["agent_id"]; ok {
mc.AgentID = agentID
}
if lastUpdate, ok := m["last_update"]; ok {
mc.LastUpdate = lastUpdate
}
} else {
return nil, errors.New("failed loading module config")
}
ci := &sConfigItem{}
if id, ok := m["id"]; ok {
ci.getConfigSchema = cl.getCb(id, "config_schema")
ci.getDefaultConfig = cl.getCb(id, "default_config")
ci.getCurrentConfig = cl.getCb(id, "current_config")
ci.setCurrentConfig = cl.setCb(id, "current_config")
ci.getEventDataSchema = cl.getCb(id, "event_data_schema")
ci.getEventConfigSchema = cl.getCb(id, "event_config_schema")
ci.getDefaultEventConfig = cl.getCb(id, "default_event_config")
ci.getCurrentEventConfig = cl.getCb(id, "current_event_config")
}
mc.IConfigItem = ci
ml = append(ml, &mc)
}
return ml, nil
}
func getStorageCb(s storage.IStorage, mpath, file string) getCallback {
return func() string {
data, err := s.ReadFile(joinPath(mpath, file))
if err == nil {
return string(data)
}
return ""
}
}
func setStorageCb(s storage.IStorage, mpath, file string) setCallback {
return func(val string) bool {
if s.WriteFile(joinPath(mpath, file), []byte(val)) != nil {
return false
}
return true
}
}
func loadConfig(s storage.IStorage, path string) ([]*loader.ModuleConfig, error) {
var mcl []*loader.ModuleConfig
cpath := strings.Replace(filepath.Join(path, "config.json"), "\\", "/", -1)
if s.IsNotExist(path) {
return nil, errors.New("directory with config not found")
}
if s.IsNotExist(cpath) {
return nil, errors.New("config file not found")
}
cdata, err := s.ReadFile(cpath)
if err != nil {
return nil, errors.New("failed read config file: " + err.Error())
}
if err = json.Unmarshal(cdata, &mcl); err != nil {
return nil, errors.New("error while reading modules config: " + err.Error())
}
for _, mc := range mcl {
mpath := joinPath(path, mc.Name, mc.Version, "config")
ci := &sConfigItem{
getConfigSchema: getStorageCb(s, mpath, "config_schema.json"),
getDefaultConfig: getStorageCb(s, mpath, "default_config.json"),
getCurrentConfig: getStorageCb(s, mpath, "current_config.json"),
setCurrentConfig: setStorageCb(s, mpath, "current_config.json"),
getEventDataSchema: getStorageCb(s, mpath, "event_data_schema.json"),
getEventConfigSchema: getStorageCb(s, mpath, "event_config_schema.json"),
getDefaultEventConfig: getStorageCb(s, mpath, "default_event_config.json"),
getCurrentEventConfig: getStorageCb(s, mpath, "current_event_config.json"),
}
mc.IConfigItem = ci
}
return mcl, nil
}
// configLoaderS3 is container for config which loaded from D3
type configLoaderS3 struct {
}
// load is function what retrieve modules config list from S3
func (cl *configLoaderS3) load() ([]*loader.ModuleConfig, error) {
s, err := storage.NewS3()
if err != nil {
return nil, errors.New("failed initialize S3 driver: " + err.Error())
}
return loadConfig(s, "/")
}
// configLoaderFS is container for config which loaded from FS
type configLoaderFS struct {
path string
}
// load is function what retrieve modules config list from FS
func (cl *configLoaderFS) load() ([]*loader.ModuleConfig, error) {
fs, err := storage.NewFS()
if err != nil {
return nil, errors.New("failed initialize FS driver: " + err.Error())
}
return loadConfig(fs, cl.path)
}
func removeLeadSlash(files map[string][]byte) map[string][]byte {
rfiles := make(map[string][]byte)
for name, data := range files {
rfiles[name[1:]] = data
}
return rfiles
}
func loadUtils(s storage.IStorage, path string) (map[string][]byte, error) {
var err error
upath := joinPath(path, "utils")
if s.IsNotExist(upath) {
return nil, errors.New("utils directory not found")
}
files, err := s.ReadDirRec(upath)
if err != nil {
return nil, errors.New("failed read utils files: " + err.Error())
}
files = removeLeadSlash(files)
return files, nil
}
func loadFiles(s storage.IStorage, path string, mcl []*loader.ModuleConfig) ([]*loader.ModuleFiles, error) {
var mfl []*loader.ModuleFiles
if s.IsNotExist(path) {
return nil, errors.New("directory with modules not found")
}
utils, err := loadUtils(s, path)
if err != nil {
return nil, err
}
for _, mc := range mcl {
var mf loader.ModuleFiles
loadModuleDir := func(dir string) (*loader.ModuleItem, error) {
mpath := joinPath(path, mc.Name, mc.Version, dir)
if s.IsNotExist(mpath) {
return nil, errors.New("module directory not found")
}
var mi loader.ModuleItem
files, err := s.ReadDirRec(mpath)
if err != nil {
return nil, errors.New("failed read module files: " + err.Error())
}
files = removeLeadSlash(files)
for p, d := range utils {
if _, ok := files[p]; !ok {
files[p] = d
}
}
args := make(map[string][]string)
if data, ok := files["args.json"]; ok {
if err = json.Unmarshal(data, &args); err != nil {
return nil, errors.New("failed read module args: " + err.Error())
}
}
mi.SetArgs(args)
mi.SetFiles(files)
return &mi, nil
}
var smi, cmi *loader.ModuleItem
if cmi, err = loadModuleDir("cmodule"); err != nil {
return nil, err
}
if smi, err = loadModuleDir("smodule"); err != nil {
return nil, err
}
mf.SetCModule(cmi)
mf.SetSModule(smi)
mfl = append(mfl, &mf)
}
return mfl, nil
}
// filesLoaderS3 is container for files structure which loaded from S3
type filesLoaderS3 struct {
}
// load is function what retrieve modules files data from S3
func (fl *filesLoaderS3) load(mcl []*loader.ModuleConfig) ([]*loader.ModuleFiles, error) {
s, err := storage.NewS3()
if err != nil {
return nil, errors.New("failed initialize S3 driver: " + err.Error())
}
return loadFiles(s, "/", mcl)
}
// filesLoaderFS is container for files structure which loaded from FS
type filesLoaderFS struct {
path string
}
// load is function what retrieve modules files data from FS
func (fl *filesLoaderFS) load(mcl []*loader.ModuleConfig) ([]*loader.ModuleFiles, error) {
fs, err := storage.NewFS()
if err != nil {
return nil, errors.New("failed initialize FS driver: " + err.Error())
}
return loadFiles(fs, fl.path, mcl)
}