2016-04-17 20:01:51 +02:00
|
|
|
package tasks
|
|
|
|
|
|
|
|
import (
|
2020-12-03 14:51:15 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/api/helpers"
|
2020-12-04 23:41:26 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
2017-02-23 00:21:49 +01:00
|
|
|
"net/http"
|
2021-10-12 21:43:15 +02:00
|
|
|
"regexp"
|
2016-04-17 20:01:51 +02:00
|
|
|
"strconv"
|
2021-10-12 21:43:15 +02:00
|
|
|
"strings"
|
2016-04-17 20:01:51 +02:00
|
|
|
"time"
|
|
|
|
|
2018-02-18 23:49:40 +01:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2019-07-09 18:14:06 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
2017-02-23 00:21:49 +01:00
|
|
|
"github.com/gorilla/context"
|
2016-04-17 20:01:51 +02:00
|
|
|
)
|
|
|
|
|
2021-10-12 21:43:15 +02:00
|
|
|
func getNextBuildVersion(startVersion string, currentVersion string) string {
|
|
|
|
re := regexp.MustCompile(`^(.*[^\d])?(\d+)([^\d].*)?$`)
|
|
|
|
m := re.FindStringSubmatch(startVersion)
|
|
|
|
|
|
|
|
if m == nil {
|
|
|
|
return startVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
var prefix, suffix, body string
|
|
|
|
|
|
|
|
switch len(m) - 1 {
|
|
|
|
case 3:
|
|
|
|
prefix = m[1]
|
|
|
|
body = m[2]
|
|
|
|
suffix = m[3]
|
|
|
|
case 2:
|
|
|
|
if _, err := strconv.Atoi(m[1]); err == nil {
|
|
|
|
body = m[1]
|
|
|
|
suffix = m[2]
|
|
|
|
} else {
|
|
|
|
prefix = m[1]
|
|
|
|
body = m[2]
|
|
|
|
}
|
|
|
|
case 1:
|
|
|
|
body = m[1]
|
|
|
|
default:
|
|
|
|
return startVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.HasPrefix(currentVersion, prefix) ||
|
|
|
|
!strings.HasSuffix(currentVersion, suffix) {
|
|
|
|
return startVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
curr, err := strconv.Atoi(currentVersion[len(prefix):len(currentVersion) - len(suffix)])
|
|
|
|
if err != nil {
|
|
|
|
return startVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
start, err := strconv.Atoi(body)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var newVer int
|
|
|
|
if start > curr {
|
|
|
|
newVer = start
|
|
|
|
} else {
|
|
|
|
newVer = curr + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return prefix + strconv.Itoa(newVer) + suffix
|
|
|
|
}
|
|
|
|
|
2021-09-06 14:01:38 +02:00
|
|
|
func AddTaskToPool(d db.Store, taskObj db.Task, userID *int, projectID int) (db.Task, error) {
|
2019-07-09 18:11:01 +02:00
|
|
|
taskObj.Created = time.Now()
|
2021-08-25 17:37:19 +02:00
|
|
|
taskObj.Status = taskWaitingStatus
|
2021-09-06 14:01:38 +02:00
|
|
|
taskObj.UserID = userID
|
|
|
|
taskObj.ProjectID = projectID
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2021-10-12 21:43:15 +02:00
|
|
|
tpl, err := d.GetTemplate(projectID, taskObj.TemplateID)
|
|
|
|
if err != nil {
|
|
|
|
return db.Task{}, err
|
|
|
|
}
|
|
|
|
if tpl.Type == "build" {
|
|
|
|
var builds []db.TaskWithTpl
|
|
|
|
builds, err = d.GetTemplateTasks(projectID, tpl.ID, db.RetrieveQueryParams{Count: 1})
|
|
|
|
if err != nil {
|
|
|
|
return db.Task{}, err
|
|
|
|
}
|
|
|
|
if len(builds) == 0 {
|
|
|
|
taskObj.Version = tpl.StartVersion
|
|
|
|
} else {
|
|
|
|
v := getNextBuildVersion(*tpl.StartVersion, *builds[0].Version)
|
|
|
|
taskObj.Version = &v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-06 14:01:38 +02:00
|
|
|
newTask, err := d.CreateTask(taskObj)
|
2021-03-12 18:41:41 +01:00
|
|
|
if err != nil {
|
2021-09-06 14:01:38 +02:00
|
|
|
return db.Task{}, err
|
2019-07-09 18:11:01 +02:00
|
|
|
}
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
pool.register <- &task{
|
2021-09-06 14:01:38 +02:00
|
|
|
store: d,
|
2021-03-12 18:41:41 +01:00
|
|
|
task: newTask,
|
2021-09-06 14:01:38 +02:00
|
|
|
projectID: projectID,
|
2019-07-09 18:11:01 +02:00
|
|
|
}
|
2019-07-09 14:56:03 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
objType := taskTypeID
|
2021-03-12 18:41:41 +01:00
|
|
|
desc := "Task ID " + strconv.Itoa(newTask.ID) + " queued for running"
|
2021-09-06 14:01:38 +02:00
|
|
|
_, err = d.CreateEvent(db.Event{
|
|
|
|
UserID: userID,
|
|
|
|
ProjectID: &projectID,
|
2019-07-09 18:11:01 +02:00
|
|
|
ObjectType: &objType,
|
2021-03-12 18:41:41 +01:00
|
|
|
ObjectID: &newTask.ID,
|
2019-07-09 18:11:01 +02:00
|
|
|
Description: &desc,
|
2020-12-01 20:06:49 +01:00
|
|
|
})
|
|
|
|
|
2021-09-06 14:01:38 +02:00
|
|
|
return newTask, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddTask inserts a task into the database and returns a header or returns error
|
|
|
|
func AddTask(w http.ResponseWriter, r *http.Request) {
|
|
|
|
project := context.Get(r, "project").(db.Project)
|
|
|
|
user := context.Get(r, "user").(*db.User)
|
|
|
|
|
|
|
|
var taskObj db.Task
|
|
|
|
|
|
|
|
if !helpers.Bind(w, r, &taskObj) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
newTask, err := AddTaskToPool(helpers.Store(r), taskObj, &user.ID, project.ID)
|
|
|
|
|
2020-12-01 20:06:49 +01:00
|
|
|
if err != nil {
|
2019-07-09 18:11:01 +02:00
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Cannot write new event to database"})
|
2020-12-01 20:06:49 +01:00
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
2019-07-09 18:11:01 +02:00
|
|
|
}
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
helpers.WriteJSON(w, http.StatusCreated, newTask)
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
|
|
|
|
2018-06-14 08:20:16 +02:00
|
|
|
// GetTasksList returns a list of tasks for the current project in desc order to limit or error
|
2017-07-25 06:44:09 +02:00
|
|
|
func GetTasksList(w http.ResponseWriter, r *http.Request, limit uint64) {
|
2020-12-04 23:41:26 +01:00
|
|
|
project := context.Get(r, "project").(db.Project)
|
2021-03-12 18:41:41 +01:00
|
|
|
tpl := context.Get(r, "template")
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2021-03-12 18:41:41 +01:00
|
|
|
var err error
|
|
|
|
var tasks []db.TaskWithTpl
|
2020-11-02 11:57:46 +01:00
|
|
|
|
2021-03-12 18:41:41 +01:00
|
|
|
if tpl != nil {
|
|
|
|
tasks, err = helpers.Store(r).GetTemplateTasks(project.ID, tpl.(db.Template).ID, db.RetrieveQueryParams{
|
|
|
|
Count: int(limit),
|
|
|
|
})
|
2020-11-02 11:57:46 +01:00
|
|
|
} else {
|
2021-03-12 18:41:41 +01:00
|
|
|
tasks, err = helpers.Store(r).GetProjectTasks(project.ID, db.RetrieveQueryParams{
|
|
|
|
Count: int(limit),
|
|
|
|
})
|
2017-07-25 06:44:09 +02:00
|
|
|
}
|
|
|
|
|
2021-03-12 18:41:41 +01:00
|
|
|
if err != nil {
|
2018-06-14 08:20:16 +02:00
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get tasks list from database"})
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
|
|
|
|
2020-12-03 14:51:15 +01:00
|
|
|
helpers.WriteJSON(w, http.StatusOK, tasks)
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// GetAllTasks returns all tasks for the current project
|
2019-07-09 18:11:01 +02:00
|
|
|
func GetAllTasks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
GetTasksList(w, r, 0)
|
2017-07-25 06:44:09 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// GetLastTasks returns the hundred most recent tasks
|
2019-07-09 18:11:01 +02:00
|
|
|
func GetLastTasks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
GetTasksList(w, r, 200)
|
2017-07-25 06:44:09 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// GetTask returns a task based on its id
|
2019-07-09 18:11:01 +02:00
|
|
|
func GetTask(w http.ResponseWriter, r *http.Request) {
|
2020-12-04 23:41:26 +01:00
|
|
|
task := context.Get(r, taskTypeID).(db.Task)
|
2020-12-03 14:51:15 +01:00
|
|
|
helpers.WriteJSON(w, http.StatusOK, task)
|
2017-05-18 14:29:57 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// GetTaskMiddleware is middleware that gets a task by id and sets the context to it or panics
|
2019-07-09 18:14:06 +02:00
|
|
|
func GetTaskMiddleware(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2021-03-12 18:41:41 +01:00
|
|
|
project := context.Get(r, "project").(db.Project)
|
2020-12-03 14:51:15 +01:00
|
|
|
taskID, err := helpers.GetIntParam("task_id", w, r)
|
2021-03-12 18:41:41 +01:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
if err != nil {
|
2021-03-12 18:41:41 +01:00
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get task_id from request"})
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
2019-07-09 18:14:06 +02:00
|
|
|
}
|
|
|
|
|
2021-03-12 18:41:41 +01:00
|
|
|
task, err := helpers.Store(r).GetTask(project.ID, taskID)
|
|
|
|
if err != nil {
|
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get task from database"})
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
2019-07-09 18:14:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
context.Set(r, taskTypeID, task)
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
|
|
|
|
2018-06-14 08:20:16 +02:00
|
|
|
// GetTaskOutput returns the logged task output by id and writes it as json or returns error
|
2019-07-09 18:11:01 +02:00
|
|
|
func GetTaskOutput(w http.ResponseWriter, r *http.Request) {
|
2020-12-04 23:41:26 +01:00
|
|
|
task := context.Get(r, taskTypeID).(db.Task)
|
2021-03-12 18:41:41 +01:00
|
|
|
project := context.Get(r, "project").(db.Project)
|
2019-07-09 14:56:03 +02:00
|
|
|
|
2020-12-04 23:41:26 +01:00
|
|
|
var output []db.TaskOutput
|
2021-03-12 18:41:41 +01:00
|
|
|
output, err := helpers.Store(r).GetTaskOutputs(project.ID, task.ID)
|
|
|
|
|
|
|
|
if err != nil {
|
2019-07-09 18:11:01 +02:00
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get task output from database"})
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2020-12-03 14:51:15 +01:00
|
|
|
helpers.WriteJSON(w, http.StatusOK, output)
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
2016-06-17 22:16:46 +02:00
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
func StopTask(w http.ResponseWriter, r *http.Request) {
|
2021-08-30 21:42:11 +02:00
|
|
|
targetTask := context.Get(r, "task").(db.Task)
|
|
|
|
project := context.Get(r, "project").(db.Project)
|
2021-08-25 17:37:19 +02:00
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
activeTask := pool.getTask(targetTask.ID)
|
2021-08-25 17:37:19 +02:00
|
|
|
|
2021-08-30 21:42:11 +02:00
|
|
|
if activeTask == nil { // task not active, but exists in database
|
|
|
|
activeTask = &task{
|
|
|
|
store: helpers.Store(r),
|
|
|
|
task: targetTask,
|
|
|
|
projectID: project.ID,
|
2021-08-25 17:37:19 +02:00
|
|
|
}
|
2021-08-30 21:42:11 +02:00
|
|
|
err := activeTask.populateDetails()
|
|
|
|
if err != nil {
|
2021-08-25 17:37:19 +02:00
|
|
|
helpers.WriteError(w, err)
|
2021-08-30 21:42:11 +02:00
|
|
|
return
|
2021-08-25 17:37:19 +02:00
|
|
|
}
|
|
|
|
|
2021-08-30 21:42:11 +02:00
|
|
|
activeTask.setStatus(taskStoppedStatus)
|
|
|
|
|
|
|
|
activeTask.createTaskEvent()
|
|
|
|
} else {
|
|
|
|
if activeTask.task.Status == taskRunningStatus {
|
|
|
|
if activeTask.process == nil {
|
|
|
|
panic("running process can not be nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := activeTask.process.Kill(); err != nil {
|
|
|
|
helpers.WriteError(w, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
activeTask.setStatus(taskStoppingStatus)
|
|
|
|
}
|
2021-08-25 17:37:19 +02:00
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// RemoveTask removes a task from the database
|
2019-07-09 18:11:01 +02:00
|
|
|
func RemoveTask(w http.ResponseWriter, r *http.Request) {
|
2021-08-31 14:03:52 +02:00
|
|
|
targetTask := context.Get(r, taskTypeID).(db.Task)
|
2020-12-04 23:41:26 +01:00
|
|
|
editor := context.Get(r, "user").(*db.User)
|
2021-03-12 18:41:41 +01:00
|
|
|
project := context.Get(r, "project").(db.Project)
|
2016-06-17 22:16:46 +02:00
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
activeTask := pool.getTask(targetTask.ID)
|
|
|
|
|
|
|
|
if activeTask != nil {
|
|
|
|
// can't delete task in queue or running
|
|
|
|
// task must be stopped firstly
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
if !editor.Admin {
|
|
|
|
log.Warn(editor.Username + " is not permitted to delete task logs")
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
|
|
return
|
|
|
|
}
|
2016-06-17 22:16:46 +02:00
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
err := helpers.Store(r).DeleteTaskWithOutputs(project.ID, targetTask.ID)
|
2021-03-12 18:41:41 +01:00
|
|
|
if err != nil {
|
|
|
|
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot delete task from database"})
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
2019-07-09 18:11:01 +02:00
|
|
|
}
|
2019-07-09 14:56:03 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2016-06-17 22:16:46 +02:00
|
|
|
}
|