|
|
|
@ -9,10 +9,12 @@ import (
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"github.com/toolkits/pkg/logger"
|
|
|
|
|
"github.com/toolkits/pkg/net/httplib"
|
|
|
|
|
"github.com/toolkits/pkg/slice"
|
|
|
|
|
|
|
|
|
|
"github.com/didi/nightingale/src/models"
|
|
|
|
|
"github.com/didi/nightingale/src/modules/job/config"
|
|
|
|
|
"github.com/didi/nightingale/src/common/address"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type taskForm struct {
|
|
|
|
@ -563,7 +565,10 @@ func taskCallback(c *gin.Context) {
|
|
|
|
|
|
|
|
|
|
// 这个数据结构是tt回调的时候使用的通用数据结构,里边既有工单基本信息,也有结构化数据,job这里只需要从中解析出结构化数据
|
|
|
|
|
type ttForm struct {
|
|
|
|
|
|
|
|
|
|
Id int64 `json:"id" binding:"required"`
|
|
|
|
|
RunUser string `json:"runUser" binding:"required"`
|
|
|
|
|
Form map[string]interface{} `json:"form" binding:"required"`
|
|
|
|
|
Approval int `json:"approval"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// /api/job-ce/run/:id?hosts=10.3.4.5,10.4.5.6
|
|
|
|
@ -571,6 +576,11 @@ func taskRunForTT(c *gin.Context) {
|
|
|
|
|
var f ttForm
|
|
|
|
|
bind(c, &f)
|
|
|
|
|
|
|
|
|
|
action := c.Request.Host + c.Request.URL.Path
|
|
|
|
|
if f.Approval == 2 {
|
|
|
|
|
renderMessage(c, "该任务未通过审批")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
tpl := TaskTpl(urlParamInt64(c, "id"))
|
|
|
|
|
arr, err := tpl.Hosts()
|
|
|
|
|
dangerous(err)
|
|
|
|
@ -587,13 +597,14 @@ func taskRunForTT(c *gin.Context) {
|
|
|
|
|
arr = tmp
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// TODO 从结构化数据中取hosts
|
|
|
|
|
hosts = "xyz"
|
|
|
|
|
hosts = strings.ReplaceAll(hosts, "\r", ",")
|
|
|
|
|
hosts = strings.ReplaceAll(hosts, "\n", ",")
|
|
|
|
|
tmp := cleanHosts(strings.Split(hosts, ","))
|
|
|
|
|
if len(tmp) > 0 {
|
|
|
|
|
arr = tmp
|
|
|
|
|
if v, ok := f.Form["hosts"]; ok {
|
|
|
|
|
hosts = v.(string)
|
|
|
|
|
hosts = strings.ReplaceAll(hosts, "\r", ",")
|
|
|
|
|
hosts = strings.ReplaceAll(hosts, "\n", ",")
|
|
|
|
|
tmp := cleanHosts(strings.Split(hosts, ","))
|
|
|
|
|
if len(tmp) > 0 {
|
|
|
|
|
arr = tmp
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -616,9 +627,144 @@ func taskRunForTT(c *gin.Context) {
|
|
|
|
|
Creator: user.Username,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO 把结构化数据转换为脚本命令行参数
|
|
|
|
|
task.Args = ""
|
|
|
|
|
for k, v := range f.Form {
|
|
|
|
|
switch v.(type) {
|
|
|
|
|
case string:
|
|
|
|
|
if k == "hosts" {
|
|
|
|
|
tmp := v.(string)
|
|
|
|
|
tmp = strings.ReplaceAll(tmp, "\r", ",")
|
|
|
|
|
tmp = strings.ReplaceAll(tmp, "\n", ",")
|
|
|
|
|
tmpArray := cleanHosts(strings.Split(hosts, ","))
|
|
|
|
|
if len(tmpArray) > 0 {
|
|
|
|
|
v = strings.Join(tmpArray, ",")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if len(v.(string)) < 1600 {
|
|
|
|
|
task.Args += fmt.Sprintf("--%s=%s,,", k, v.(string))
|
|
|
|
|
}
|
|
|
|
|
case int:
|
|
|
|
|
task.Args += fmt.Sprintf("--%s=%d,,", k, v.(int))
|
|
|
|
|
case int64:
|
|
|
|
|
task.Args += fmt.Sprintf("--%s=%d,,", k, v.(int64))
|
|
|
|
|
case float64:
|
|
|
|
|
//TODO 暂时不支持传非整型
|
|
|
|
|
task.Args += fmt.Sprintf("--%s=%d,,", k, int64(v.(float64)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
task.Args = strings.TrimSuffix(task.Args, ",,")
|
|
|
|
|
|
|
|
|
|
dangerous(task.Save(arr, "start"))
|
|
|
|
|
renderData(c, task.Id, nil)
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
var (
|
|
|
|
|
restHosts []string
|
|
|
|
|
)
|
|
|
|
|
for _, h := range arr {
|
|
|
|
|
th, err := models.TaskHostGet(task.Id, h)
|
|
|
|
|
if err == nil {
|
|
|
|
|
if th.Status == "killed" {
|
|
|
|
|
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行失败,"+
|
|
|
|
|
"原因为task被kill掉\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
|
|
|
|
|
task.Id, h, action, th.Stdout, th.Stderr)
|
|
|
|
|
err = TicketSender(f.Id, action, "task has been killed", reply, -1,
|
|
|
|
|
nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("send callback to ticket, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else if th.Status == "failed" {
|
|
|
|
|
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行失败,"+
|
|
|
|
|
"详情见错误输出\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
|
|
|
|
|
task.Id, h, action, th.Stdout, th.Stderr)
|
|
|
|
|
err = TicketSender(f.Id, action, "run task failed", reply, -1,
|
|
|
|
|
nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("send callback to ticket, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else if th.Status == "timeout" {
|
|
|
|
|
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行超时"+
|
|
|
|
|
"\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
|
|
|
|
|
task.Id, h, action, th.Stdout, th.Stderr)
|
|
|
|
|
err = TicketSender(f.Id, action, "run task failed", reply, -1,
|
|
|
|
|
nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("send callback to ticket, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else if th.Status == "success" {
|
|
|
|
|
reply := fmt.Sprintf("### Job通知推送\n* Job平台任务(ID:%d)在机器%s中执行成功"+
|
|
|
|
|
"\n* 执行action接口地址为: %s\n* 标准输出: %s\n* 错误输出: %s\n",
|
|
|
|
|
task.Id, h, action, th.Stdout, th.Stderr)
|
|
|
|
|
err = TicketSender(f.Id, action, "task ", reply, 1,
|
|
|
|
|
nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("send callback to ticket, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
restHosts = append(restHosts, h)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
logger.Errorf("get task_host err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
arr = restHosts
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
reply := fmt.Sprintf("[任务详情请关注Job平台任务(ID:%d)详情页地址](%s)", task.Id, fmt.Sprintf("/job/tasks/%d/result", task.Id))
|
|
|
|
|
err = TicketSender(f.Id, action, "", reply, -1,
|
|
|
|
|
nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("send callback to ticket, err: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
renderData(c, gin.H{"taskID": task.Id, "detailPage": fmt.Sprintf("/job/tasks/%d/result", task.Id)}, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ticketCallBackForm struct {
|
|
|
|
|
TicketId int64 `json:"ticketId" binding:"required"`
|
|
|
|
|
ActionApi string `json:"actionApi" binding:"required"`
|
|
|
|
|
SystemName string `json:"systemName" binding:"required"`
|
|
|
|
|
Success int `json:"success" binding:"required"`
|
|
|
|
|
Reason string `json:"reason"`
|
|
|
|
|
Info interface{} `json:"info"`
|
|
|
|
|
AutoReply string `json:"autoReply"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TicketSender(id int64, action, reason, reply string, result int, info interface{}) error {
|
|
|
|
|
addr := address.GetHTTPListen("ticket")
|
|
|
|
|
|
|
|
|
|
data := ticketCallBackForm{
|
|
|
|
|
TicketId: id,
|
|
|
|
|
ActionApi: action,
|
|
|
|
|
Success: result,
|
|
|
|
|
Reason: reason,
|
|
|
|
|
Info: info,
|
|
|
|
|
AutoReply: reply,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url := fmt.Sprintf("%s/v1/ticket/callback?systemName=job", addr)
|
|
|
|
|
if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) {
|
|
|
|
|
url = "http://" + url
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res, code, err := httplib.PostJSON(url, time.Second*5, data, map[string]string{"x-srv-token": "ticket-builtin-token"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Errorf("call sender api failed, server: %v, data: %+v, err: %v, resp:%v, status code:%d", url, data, err, string(res), code)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if code != 200 {
|
|
|
|
|
logger.Errorf("call sender api failed, server: %v, data: %+v, resp:%v, code:%d", url, data, string(res), code)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.Debugf("ticket response %s", string(res))
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|