Copyright (c) 2024 Huawei Technologies Co., Ltd.
openFuyao is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/
package checker
import (
"encoding/json"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
"openfuyao.com/oscheck/internal/conf"
"openfuyao.com/oscheck/internal/utils"
)
type Plugin struct {
Name string `json:"name"`
Execute string `json:"execute"`
Input string `json:"input"`
Output string `json:"output"`
filepath string `json:"-"`
}
type Plugins struct {
Plugins []Plugin `json:"plugins"`
}
func loadPlugins() {
logger := utils.GetLogger("plugin")
plugins, err := findPlugins(logger)
if err != nil {
logger.Printf("find plugins error: %v", err)
return
}
existPlugins := map[string]Plugin{}
for _, plugin := range plugins {
plugin.Execute = filepath.Join(filepath.Dir(plugin.filepath), plugin.Execute)
if plugin.Execute == "" {
logger.Printf("plugin execute is empty, plugin: %v", plugin)
continue
}
if !filepath.IsAbs(plugin.Execute) {
plugin.Execute = filepath.Join(utils.GetStartDir(), "plugin", plugin.Execute)
}
stat, err := os.Stat(plugin.Execute)
if err != nil {
logger.Printf("plugin execute file not exist, plugin: %v, err: %v", plugin, err)
continue
}
if !stat.IsDir() && stat.Mode()&utils.FileModeExecutable == 0 {
logger.Printf("plugin execute is not executable, plugin: %s, config file: %s, execute: %s",
plugin.Name,
plugin.filepath,
plugin.Execute)
continue
}
if existPlugin, ok := existPlugins[plugin.Name]; ok {
logger.Printf("plugin name dupllicate, plugin: %s, file:<%s>, <%s>, skip the second", plugin.Name,
existPlugin.filepath,
plugin.filepath)
continue
}
RegisterCheckerFactory(plugin.Name, func() Checker {
return &PluginChecker{conf: plugin}
})
}
}
func findPlugins(logger *log.Logger) ([]Plugin, error) {
plugins := make([]Plugin, 0)
walkFn := func(path string, info fs.FileInfo, err error) error {
if err != nil {
logger.Printf("walk plugins file error, file: <%s>: %v", path, err)
return err
}
if info.IsDir() || !(strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
logger.Printf("read plugins file error, file: <%s>: %v", path, err)
return nil
}
pluginSpec := Plugins{}
if err := yaml.Unmarshal(content, &pluginSpec); err != nil {
logger.Printf("unmarshal plugins file error, file: <%s>: %v", path, err)
return nil
}
for _, plugin := range pluginSpec.Plugins {
plugin.filepath = path
plugins = append(plugins, plugin)
}
return nil
}
if err := filepath.Walk(filepath.Join(utils.GetStartDir(), "plugin"), walkFn); err != nil {
return nil, err
}
return plugins, nil
}
type PluginChecker struct {
conf Plugin
item conf.CheckItem
ctx Context
logger *log.Logger
}
func (p *PluginChecker) Check() ItemRslt {
rslt := ItemRslt{
Result: ResultValid,
}
if p.conf.Execute == "" {
return rslt
}
input, err := p.getInput()
if err != nil {
p.logger.Printf("get plugin input error, plugin: %s, err: %v", p.conf.Name, err)
rslt.Result = ResultError
return rslt
}
exec := utils.Command{
Command: "sh",
Args: []string{"-c", p.conf.Execute},
RootPath: p.ctx.RootPath,
CombineOutput: false,
Stdin: input,
Logger: p.logger,
}
execResult, err := exec.Exec()
if err != nil {
p.logger.Printf("exec plugin error, plugin: %s, err: %v", p.conf.Name, err)
rslt.Result = ResultError
return rslt
}
switch p.conf.Output {
case "none":
if execResult.ExitCode != 0 {
rslt.Result = ResultInvalid
}
case "json":
if err := json.Unmarshal([]byte(execResult.Stdout), &rslt); err != nil {
p.logger.Printf("unmarshal plugin output error, plugin: %s, err: %v, stdout: %s, stderr:%s", p.conf.Name,
err, execResult.Stdout, execResult.Stderr)
rslt.Result = ResultError
}
case "yaml":
if err := yaml.Unmarshal([]byte(execResult.Stdout), &rslt); err != nil {
p.logger.Printf("unmarshal plugin output error, plugin: %s, err: %v, stdout: %s, stderr:%s", p.conf.Name,
err, execResult.Stdout, execResult.Stderr)
rslt.Result = ResultError
}
default:
p.logger.Printf("unknown plugin output type, plugin: %s, type: %s", p.conf.Name, p.conf.Output)
rslt.Result = ResultError
}
rslt.Key = p.item.Name
rslt.Doc = p.item.Doc
return rslt
}
func (p *PluginChecker) getInput() (string, error) {
var inputBytes []byte
var err error
switch p.conf.Input {
case "json":
inputBytes, err = json.Marshal(p.item)
case "yaml":
inputBytes, err = yaml.Marshal(p.item)
default:
return "", nil
}
if err != nil {
return "", err
}
return string(inputBytes), nil
}
func (p *PluginChecker) Init(ctx Context, item conf.CheckItem) *ParamCheckError {
p.ctx = ctx
p.item = item
p.logger = utils.GetLogger("plugin-" + p.conf.Name)
return nil
}
func init() {
loadPlugins()
}