日常工作中可能有一些按顺序执行脚本的需求, 出现错误时可能还需要回滚, 这时候就需要一个工具来帮助我们完成这些工作。 Executor
就是这样一个工具。
下面和大家分享一下这个小工具的实现及使用。
工具项目地址: https://github.com/Forget-C/gopkg
工具目录:/pkg/scripts
代码实现比较简单。
pkg|main ⇒ tree scripts
scripts
├── README.md
├── checker.go
├── executor.go
├── executor_test.go
├── finder.go
└── finder_test.go
executor.go
文件中定义了脚本执行器相关的结构体和方法。
type Executor interface {
// Run run script and return success, output, error
// if out is nil, output will be return in []byte
Run(ctx context.Context, out io.Writer) (bool, []byte, error)
// MakeCMD make exec.Cmd
// some executor don't need this method
MakeCMD(ctx context.Context, out io.Writer) *exec.Cmd
// ScriptType return script type
ScriptType() ScriptType
}
MakeExecutor
方法会根据文件后缀创建对应的脚本执行器,目前支持的脚本类型有:python
、sql
、bash
、二进制可执行文件
。
func MakeExecutor(filePath string) Executor {
ext := filepath.Ext(filePath)
switch ext {
case ".py":
return NewPythonExecutor(filePath, nil)
case ".sh", ".bash":
return NewBashExecutor(filePath, nil)
case ".sql":
return NewSQLExecutor(filePath)
case "", ".bin":
return NewBinaryExecutor(filePath, nil)
default:
return nil
}
}
ExecutorImpl
实现了基础方法
func (e *ExecutorImpl) Run(ctx context.Context, out io.Writer) (bool, []byte, error) {
consoleOut := true
_out := out
if _out == nil {
consoleOut = false
_out = &bytes.Buffer{}
}
// 命令执行的模式
cmd := e.MakeCMD(ctx, _out)
err := cmd.Run()
var outRes []byte
if !consoleOut {
outRes = _out.(*bytes.Buffer).Bytes()
}
if err != nil {
return false, outRes, err
}
return true, outRes, nil
}
SQLExecutor
是单独实现的方法,因为他不是使用运行命令的方式执行脚本, 而是使用了go-sql-driver
来执行脚本。
func (e *SQLExecutor) SetConfig(cfg *viper.Viper) {
// 存储mysql连接配置信息
e.Config = cfg
}
func (e *SQLExecutor) Run(ctx context.Context, out io.Writer) (bool, []byte, error) {
returner := func(err error) (bool, []byte, error) {
if out != nil {
if err != nil {
out.Write([]byte(err.Error()))
return false, nil, err
} else {
fmt.Fprintf(out, "run script: %s success", e.ScriptPath)
return true, nil, nil
}
} else {
if err != nil {
return false, []byte(err.Error()), err
}
}
return true, nil, nil
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
e.Config.GetString("MYSQL_USER"),
e.Config.GetString("MYSQL_PASSWORD"),
e.Config.GetString("MYSQL_HOST"),
e.Config.GetInt("MYSQL_PORT"),
e.Config.GetString("MYSQL_DATABASE"),
)
sqlScript, err := os.ReadFile(e.ScriptPath)
if err != nil {
return returner(err)
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return returner(err)
}
defer db.Close()
_, err = db.ExecContext(ctx, string(sqlScript))
if err != nil {
return returner(err)
}
return returner(nil)
}
你可以实现自己的MakeExecutor
和Executor
来支持更多脚本类型
finder.go
文件中定义了脚本查找器和管理器相关的结构体和方法。
Scan
方法会扫描指定目录下的所有脚本文件,并按照顺序返回脚本列表。
脚本的命名规则:
ScriptManager
提供脚本管理的相关方法,用于执行脚本。
ScriptManager
将脚本定义为三个阶段, pre
、run
和post
ScriptManager.checkers
用于检查是否满足脚本执行条件以及初始化一些数据到stat
中。
ScriptManager.getters
用于从stat
中获取指定数据
func NewScriptManager(baseDir string, logger *log.Logger, out io.Writer) *ScriptManager {
return &ScriptManager{
BaseDir: baseDir,
Logger: logger,
SkipE: false,
Out: out,
SkipRollbackE: true,
scripts: make(map[int]Scripts),
State: make(map[string]interface{}),
getters: []PrepareFunc{
MysqlPreGetter,
},
checkers: []PrepareFunc{
MysqlPreChecker,
},
}
}
SQLExecutor
中的参数初始化和参数获取就是通过这两个方法来实现的。 具体实现在checker.go
中。
这两个方法需要显式调用ScriptManager.Prepare
方法来执行。
cli程序目录: /cmd/executor/executor.go
executor用于脚本执行工作。
例如:初始化数据库,初始化配置文件等。
executor
将会执行指定目录中的所有脚本。
程序将会在--base-dir
指定的目录下查找scripts目录,执行scripts目录下的所有脚本。
脚本的执行顺序为:pre_init目录下的脚本 -> init目录下的脚本 -> post-init目录下的脚本。
脚本的命名规则:
正常执行的脚本将按照文件名前的数字顺序执行,例如:001-init.sh -> 002-init.sh -> 003-init.sh。
回滚脚本是在正常执行的脚本执行失败时执行的, 将会在失败的序号开始,逆序执行rollback脚本:
如当前执行到 002_init.sh , 此脚本执行失败, 那么将会执行 002_init_rollback.sh -> 001_init_rollback.sh
脚本类型是通过文件后缀判断的, 目前支持 python、sql、bash、二进制可执行文件
当有SQL
类型脚本需要设置环境变量提供连接信息:
--help
查看帮助
gopkg|master⚡ ⇒ ./executor --help
_
___ __ __ ___ ___ _ _ | |_ ___ _ __
/ _ \ \ \/ / / _ \ / __| | | | | | __| / _ \ | '__|
| __/ > < | __/ | (__ | |_| | | |_ | (_) | | |
\___| /_/\_\ \___| \___| \__,_| \__| \___/ |_|
The executor is script run/management tools.
For example: initialize the database, initialize the configuration file, etc.
The program will find the scripts directory in the base-dir directory and execute all scripts in the scripts directory.
The script is executed in the following order: scripts in the pre_init directory-> scripts in the init directory-> scripts in the post-init directory.
The script will be executed according to the number sequence before the file name, for example: 001-init.sh -> 002-init.sh -> 003-init.sh.
If there is no number before the script file name, it will be executed in the dictionary order of the file name, for example: a-init.sh -> b-init.sh -> c-init.sh.
Any script execution fails and the program will exit.
Usage:
executor [flags]
Flags:
--base-dir string The base directory for finding default files. (default "./scripts")
--enable-post-scripts Enable post-run scripts. (default true)
--enable-pre-scripts Enable pre-run scripts. (default true)
-h, --help help for executor
--skip-error Skip errors when exec error.
--skip-rollback-error Skip errors when exec rollback error. (default true)