Semaphore/api/tasks/http.go
2021-10-13 00:43:15 +05:00

292 lines
7.2 KiB
Go

package tasks
import (
"github.com/ansible-semaphore/semaphore/api/helpers"
"github.com/ansible-semaphore/semaphore/db"
"net/http"
"regexp"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/ansible-semaphore/semaphore/util"
"github.com/gorilla/context"
)
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
}
func AddTaskToPool(d db.Store, taskObj db.Task, userID *int, projectID int) (db.Task, error) {
taskObj.Created = time.Now()
taskObj.Status = taskWaitingStatus
taskObj.UserID = userID
taskObj.ProjectID = projectID
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
}
}
newTask, err := d.CreateTask(taskObj)
if err != nil {
return db.Task{}, err
}
pool.register <- &task{
store: d,
task: newTask,
projectID: projectID,
}
objType := taskTypeID
desc := "Task ID " + strconv.Itoa(newTask.ID) + " queued for running"
_, err = d.CreateEvent(db.Event{
UserID: userID,
ProjectID: &projectID,
ObjectType: &objType,
ObjectID: &newTask.ID,
Description: &desc,
})
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)
if err != nil {
util.LogErrorWithFields(err, log.Fields{"error": "Cannot write new event to database"})
w.WriteHeader(http.StatusInternalServerError)
return
}
helpers.WriteJSON(w, http.StatusCreated, newTask)
}
// GetTasksList returns a list of tasks for the current project in desc order to limit or error
func GetTasksList(w http.ResponseWriter, r *http.Request, limit uint64) {
project := context.Get(r, "project").(db.Project)
tpl := context.Get(r, "template")
var err error
var tasks []db.TaskWithTpl
if tpl != nil {
tasks, err = helpers.Store(r).GetTemplateTasks(project.ID, tpl.(db.Template).ID, db.RetrieveQueryParams{
Count: int(limit),
})
} else {
tasks, err = helpers.Store(r).GetProjectTasks(project.ID, db.RetrieveQueryParams{
Count: int(limit),
})
}
if err != nil {
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get tasks list from database"})
w.WriteHeader(http.StatusBadRequest)
return
}
helpers.WriteJSON(w, http.StatusOK, tasks)
}
// GetAllTasks returns all tasks for the current project
func GetAllTasks(w http.ResponseWriter, r *http.Request) {
GetTasksList(w, r, 0)
}
// GetLastTasks returns the hundred most recent tasks
func GetLastTasks(w http.ResponseWriter, r *http.Request) {
GetTasksList(w, r, 200)
}
// GetTask returns a task based on its id
func GetTask(w http.ResponseWriter, r *http.Request) {
task := context.Get(r, taskTypeID).(db.Task)
helpers.WriteJSON(w, http.StatusOK, task)
}
// GetTaskMiddleware is middleware that gets a task by id and sets the context to it or panics
func GetTaskMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
project := context.Get(r, "project").(db.Project)
taskID, err := helpers.GetIntParam("task_id", w, r)
if err != nil {
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get task_id from request"})
w.WriteHeader(http.StatusBadRequest)
return
}
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
}
context.Set(r, taskTypeID, task)
next.ServeHTTP(w, r)
})
}
// GetTaskOutput returns the logged task output by id and writes it as json or returns error
func GetTaskOutput(w http.ResponseWriter, r *http.Request) {
task := context.Get(r, taskTypeID).(db.Task)
project := context.Get(r, "project").(db.Project)
var output []db.TaskOutput
output, err := helpers.Store(r).GetTaskOutputs(project.ID, task.ID)
if err != nil {
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot get task output from database"})
w.WriteHeader(http.StatusBadRequest)
return
}
helpers.WriteJSON(w, http.StatusOK, output)
}
func StopTask(w http.ResponseWriter, r *http.Request) {
targetTask := context.Get(r, "task").(db.Task)
project := context.Get(r, "project").(db.Project)
activeTask := pool.getTask(targetTask.ID)
if activeTask == nil { // task not active, but exists in database
activeTask = &task{
store: helpers.Store(r),
task: targetTask,
projectID: project.ID,
}
err := activeTask.populateDetails()
if err != nil {
helpers.WriteError(w, err)
return
}
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)
}
w.WriteHeader(http.StatusNoContent)
}
// RemoveTask removes a task from the database
func RemoveTask(w http.ResponseWriter, r *http.Request) {
targetTask := context.Get(r, taskTypeID).(db.Task)
editor := context.Get(r, "user").(*db.User)
project := context.Get(r, "project").(db.Project)
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
}
if !editor.Admin {
log.Warn(editor.Username + " is not permitted to delete task logs")
w.WriteHeader(http.StatusUnauthorized)
return
}
err := helpers.Store(r).DeleteTaskWithOutputs(project.ID, targetTask.ID)
if err != nil {
util.LogErrorWithFields(err, log.Fields{"error": "Bad request. Cannot delete task from database"})
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
}