增加本地命令执行

pull/21/merge
ouqiang 2017-04-17 02:01:41 +08:00
parent dcf86e00a3
commit f99db815de
7 changed files with 48 additions and 39 deletions

View File

@ -9,6 +9,7 @@ type TaskProtocol int8
const ( const (
TaskHTTP TaskProtocol = iota + 1 // HTTP协议 TaskHTTP TaskProtocol = iota + 1 // HTTP协议
TaskSSH // SSH命令 TaskSSH // SSH命令
TaskLocalCommand // 本地命令
) )
// 任务 // 任务
@ -16,7 +17,7 @@ type Task struct {
Id int `xorm:"int pk autoincr"` Id int `xorm:"int pk autoincr"`
Name string `xorm:"varchar(64) notnull"` // 任务名称 Name string `xorm:"varchar(64) notnull"` // 任务名称
Spec string `xorm:"varchar(64) notnull"` // crontab Spec string `xorm:"varchar(64) notnull"` // crontab
Protocol TaskProtocol `xorm:"tinyint notnull"` // 协议 1:http 2:ssh-command Protocol TaskProtocol `xorm:"tinyint notnull"` // 协议 1:http 2:ssh-command 3: 本地命令
Command string `xorm:"varchar(512) notnull"` // URL地址或shell命令 Command string `xorm:"varchar(512) notnull"` // URL地址或shell命令
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制 Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
HostId int16 `xorm:"smallint notnull default 0"` // SSH host id HostId int16 `xorm:"smallint notnull default 0"` // SSH host id

View File

@ -25,6 +25,7 @@ func init() {
} }
func InitEnv() { func InitEnv() {
runtime.GOMAXPROCS(runtime.NumCPU())
logger.InitLogger() logger.InitLogger()
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {

View File

@ -23,9 +23,26 @@ type Result struct {
// 执行shell命令 // 执行shell命令
func Exec(sshConfig SSHConfig, cmd string) (output string, err error) { func Exec(sshConfig SSHConfig, cmd string) (output string, err error) {
client, err := getClient(sshConfig)
if err != nil {
return "", err
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
var resultChan chan Result = make(chan Result) var resultChan chan Result = make(chan Result)
var timeoutChan chan bool = make(chan bool) var timeoutChan chan bool = make(chan bool)
go execCommand(sshConfig, cmd, resultChan) go func() {
cmd += fmt.Sprintf(" & { sleep %d; eval 'kill $!' &> /dev/null; }", sshConfig.ExecTimeout)
output, err := session.CombinedOutput(cmd)
resultChan <- Result{string(output), err}
}()
go triggerTimeout(timeoutChan, sshConfig.ExecTimeout) go triggerTimeout(timeoutChan, sshConfig.ExecTimeout)
select { select {
case result := <- resultChan: case result := <- resultChan:
@ -55,24 +72,6 @@ func getClient(sshConfig SSHConfig) (*ssh.Client, error) {
return ssh.Dial("tcp", addr, config) return ssh.Dial("tcp", addr, config)
} }
func execCommand(sshConfig SSHConfig, cmd string, result chan Result) {
client, err := getClient(sshConfig)
if err != nil {
result <- Result{"", err}
return
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
result <- Result{"", err}
return
}
defer session.Close()
output, err := session.CombinedOutput(cmd)
result <- Result{string(output), err}
}
func triggerTimeout(ch chan bool, timeout int){ func triggerTimeout(ch chan bool, timeout int){
// 最长执行时间不能超过24小时 // 最长执行时间不能超过24小时

View File

@ -37,7 +37,7 @@ func Create(ctx *macaron.Context) {
type TaskForm struct { type TaskForm struct {
Name string `binding:"Required;"` Name string `binding:"Required;"`
Spec string `binding:"Required;MaxSize(64)"` Spec string `binding:"Required;MaxSize(64)"`
Protocol models.TaskProtocol `binding:"In(1,2)"` Protocol models.TaskProtocol `binding:"In(1,2,3)"`
Command string `binding:"Required;MaxSize(512)"` Command string `binding:"Required;MaxSize(512)"`
Timeout int `binding:"Range(0,86400)"` Timeout int `binding:"Range(0,86400)"`
HostId int16 HostId int16

View File

@ -9,6 +9,8 @@ import (
"github.com/ouqiang/gocron/modules/logger" "github.com/ouqiang/gocron/modules/logger"
"github.com/ouqiang/gocron/modules/ssh" "github.com/ouqiang/gocron/modules/ssh"
"github.com/jakecoffman/cron" "github.com/jakecoffman/cron"
"github.com/ouqiang/gocron/modules/utils"
"strings"
) )
var Cron *cron.Cron var Cron *cron.Cron
@ -54,6 +56,19 @@ type Handler interface {
Run(taskModel models.TaskHost) (string, error) Run(taskModel models.TaskHost) (string, error)
} }
type LocalCommandHandler struct {}
func (h *LocalCommandHandler) Run(taskModel models.TaskHost) (string, error) {
args := strings.Split(taskModel.Command, " ")
if len(args) > 1 {
return utils.ExecShell(args[0], args[1:]...)
}
return utils.ExecShell(args[0])
}
// HTTP任务 // HTTP任务
type HTTPHandler struct{} type HTTPHandler struct{}
@ -142,6 +157,8 @@ func createHandlerJob(taskModel models.TaskHost) cron.FuncJob {
handler = new(HTTPHandler) handler = new(HTTPHandler)
case models.TaskSSH: case models.TaskSSH:
handler = new(SSHCommandHandler) handler = new(SSHCommandHandler)
case models.TaskLocalCommand:
handler = new(LocalCommandHandler)
} }
if handler == nil { if handler == nil {
return nil return nil
@ -152,17 +169,7 @@ func createHandlerJob(taskModel models.TaskHost) cron.FuncJob {
logger.Error("任务开始执行#写入任务日志失败-", err) logger.Error("任务开始执行#写入任务日志失败-", err)
return return
} }
// err != nil 执行失败, 失败重试3次 result, err := handler.Run(taskModel)
retryTime := 4
var result string = ""
for i := 0; i < retryTime; i++ {
result, err = handler.Run(taskModel)
if err == nil {
break
} else {
logger.Error("任务执行失败#tasklog.id-" + strconv.FormatInt(taskLogId, 10) + "#尝试次数-" + strconv.Itoa(i + 1) + "#" + err.Error() + " " + result)
}
}
_, err = updateTaskLog(taskLogId, result, err) _, err = updateTaskLog(taskLogId, result, err)
if err != nil { if err != nil {
logger.Error("任务结束#更新任务日志失败-", err) logger.Error("任务结束#更新任务日志失败-", err)

View File

@ -42,7 +42,7 @@
<tr> <tr>
<td>{{{.Name}}}</td> <td>{{{.Name}}}</td>
<td>{{{.Spec}}}</td> <td>{{{.Spec}}}</td>
<td>{{{if eq .Protocol 1}}} HTTP {{{else}}} SSH {{{end}}}</td> <td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SSH {{{else}}} 本地命令 {{{end}}}</td>
<td>{{{.Timeout}}}</td> <td>{{{.Timeout}}}</td>
<td>{{{.Hostname}}}</td> <td>{{{.Hostname}}}</td>
<td> <td>

View File

@ -32,10 +32,11 @@
<div class="field"> <div class="field">
<label>协议</label> <label>协议</label>
<div class="ui dropdown selection"> <div class="ui dropdown selection">
<input type="hidden" name="protocol" value="2"> <input type="hidden" name="protocol" value="3">
<div class="default text">SSH-Command (执行shell命令)</div> <div class="default text">本地命令</div>
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<div class="item active" data-value="3">本地命令</div>
<div class="item active" data-value="2">SSH</div> <div class="item active" data-value="2">SSH</div>
<div class="item" data-value="1">HTTP</div> <div class="item" data-value="1">HTTP</div>
</div> </div>